Make ESLint configuration stricter and remove JSHint and JSCS.

This commit is contained in:
XhmikosR 2016-03-07 21:26:40 +02:00
Родитель a9506ce660
Коммит 15e48d40b8
19 изменённых файлов: 1061 добавлений и 2150 удалений

302
.eslintrc
Просмотреть файл

@ -1,58 +1,254 @@
{
"root": true,
"extends": "eslint:recommended",
"rules": {
"array-bracket-spacing": [2, "never"],
"block-scoped-var": 2,
"brace-style": [2, "stroustrup"],
"camelcase": 2,
"comma-spacing": [2, {"before": false, "after": true}],
"comma-style": [2, "last"],
"computed-property-spacing": [2, "never"],
"consistent-this": [2, "self"],
"curly": 2,
"eol-last": 2,
"eqeqeq": 2,
"key-spacing": [2, {"beforeColon": false, "afterColon": true}],
"keyword-spacing": [2, {"before": true, "after": true}],
"new-cap": 2,
"new-parens": 2,
"no-array-constructor": 2,
"no-bitwise": 2,
"no-constant-condition": 0,
"no-dupe-args": 2,
"no-duplicate-case": 2,
"no-eval": 2,
"no-floating-decimal": 2,
"no-implied-eval": 2,
"no-inline-comments": 0,
"no-irregular-whitespace": 2,
"no-mixed-spaces-and-tabs": 2,
"no-multi-spaces": 2,
"no-multiple-empty-lines": 0,
"no-new-object": 2,
"no-process-env": 2,
"no-reserved-keys": 0,
"no-self-compare": 2,
"no-spaced-func": 2,
"no-throw-literal": 2,
"no-trailing-spaces": 2,
"no-underscore-dangle": 0,
"no-void": 2,
"object-curly-spacing": [2, "never"],
"one-var": [2, "never"],
"operator-assignment": [2, "always"],
"padded-blocks": 0,
"quotes": 0,
"radix": 2,
"semi": [2, "always"],
"semi-spacing": [2, {"before": false, "after": true}],
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
"spaced-comment": [0, "always"],
"space-in-parens": [2, "never"],
"space-unary-ops": [2, {"words": true, "nonwords": false}],
"vars-on-top": 0,
"wrap-iife": [2, "inside"],
"yoda": [2, "never"]
"array-bracket-spacing": "error",
"array-callback-return": "error",
"block-scoped-var": "error",
"block-spacing": "error",
"brace-style": [
"error",
"1tbs"
],
"callback-return": "error",
"camelcase": "error",
"comma-dangle": "error",
"comma-spacing": [
"error",
{
"before": false,
"after": true
}
],
"comma-style": [
"error",
"last"
],
"consistent-return": "error",
"consistent-this": [
"error",
"this"
],
"curly": "error",
"default-case": "error",
"dot-notation": "error",
"eol-last": "error",
"eqeqeq": "error",
"func-call-spacing": "error",
"func-names": "off",
"func-style": [
"off",
"declaration"
],
"global-require": "off",
"guard-for-in": "error",
"handle-callback-err": "error",
"indent": [
"error",
4,
{
"SwitchCase": 1
}
],
"init-declarations": [
"off",
"always"
],
"key-spacing": [
"error",
{
"beforeColon": false,
"afterColon": true,
"mode": "minimum"
}
],
"keyword-spacing": [
"error",
{
"before": true,
"after": true
}
],
"max-statements-per-line": [
"error",
{
"max": 1
}
],
"multiline-ternary": "off",
"new-cap": "error",
"newline-after-var": [
"off",
"always"
],
"newline-per-chained-call": [
"off",
{
"ignoreChainWithDepth": 3
}
],
"new-parens": "error",
"no-alert": "error",
"no-array-constructor": "error",
"no-bitwise": "error",
"no-caller": "error",
"no-catch-shadow": "error",
"no-confusing-arrow": "error",
"no-console": "off",
"no-constant-condition": ["error",
{
"checkLoops": false
}
],
"no-div-regex": "error",
"no-duplicate-imports": "error",
"no-else-return": "error",
"no-empty": "error",
"no-empty-function": "error",
"no-eq-null": "error",
"no-eval": "error",
"no-extra-bind": "error",
"no-extra-label": "error",
"no-extra-parens": "error",
"no-floating-decimal": "error",
"no-global-assign": "error",
"no-implicit-coercion": "error",
"no-implicit-globals": "error",
"no-implied-eval": "error",
"no-inline-comments": "off",
"no-label-var": "error",
"no-lone-blocks": "error",
"no-lonely-if": "error",
"no-loop-func": "error",
"no-magic-numbers": [
"off",
{
"ignoreArrayIndexes": true
}
],
"no-mixed-operators": "off",
"no-mixed-requires": "error",
"no-multiple-empty-lines": "error",
"no-multi-spaces": [
"error",
{
"ignoreEOLComments": true,
"exceptions": {
"Property": true,
"VariableDeclarator": true
}
}
],
"no-multi-str": "error",
"no-negated-condition": "error",
"no-nested-ternary": "error",
"no-new-object": "error",
"no-octal": "error",
"no-param-reassign": [
"off",
{
"props": false
}
],
"no-path-concat": "error",
"no-return-assign": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-shadow": "error",
"no-shadow-restricted-names": "error",
"no-throw-literal": "error",
"no-trailing-spaces": "error",
"no-undefined": "off",
"no-undef-init": "error",
"no-underscore-dangle": "off",
"no-unmodified-loop-condition": "error",
"no-unneeded-ternary": "error",
"no-unsafe-negation": "error",
"no-unused-expressions": "error",
"no-unused-labels": "error",
"no-use-before-define": "error",
"no-useless-call": "error",
"no-useless-computed-key": "error",
"no-useless-concat": "error",
"no-useless-constructor": "error",
"no-useless-escape": "error",
"no-useless-rename": "error",
"no-void": "error",
"no-whitespace-before-property": "error",
"no-with": "error",
"object-curly-newline": [
"off",
{
"multiline": true,
"minProperties": 2
}
],
"object-curly-spacing": [
"error",
"never"
],
"object-property-newline": "error",
"one-var": [
"error",
"never"
],
"one-var-declaration-per-line": [
"error",
"always"
],
"operator-assignment": [
"error",
"always"
],
"operator-linebreak": [
"error",
"after"
],
"quote-props": [
"error",
"consistent"
],
"quotes": [
"error",
"single"
],
"radix": "error",
"semi": [
"error",
"always"
],
"semi-spacing": "error",
"space-before-blocks": [
"error",
"always"
],
"spaced-comment": [
"off",
"always"
],
"space-infix-ops": "error",
"space-in-parens": [
"error",
"never"
],
"space-unary-ops": "error",
"strict": [
"off",
"safe"
],
"unicode-bom": [
"error",
"never"
],
"use-isnan": "error",
"valid-jsdoc": "error",
"wrap-iife": [
"error",
"inside"
],
"yoda": [
"error",
"never"
]
}
}

38
.jscsrc
Просмотреть файл

@ -1,38 +0,0 @@
{
"disallowEmptyBlocks": true,
"disallowKeywords": ["with"],
"disallowMixedSpacesAndTabs": true,
"disallowMultipleLineStrings": true,
"disallowMultipleVarDecl": true,
"disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
"disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
"disallowSpacesInCallExpression": true,
"disallowSpacesInsideBrackets": true,
"disallowTrailingWhitespace": true,
"requireCapitalizedConstructors": true,
"requireCommaBeforeLineBreak": true,
"requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch", "case", "default"],
"requireLineFeedAtFileEnd": true,
"requireOperatorBeforeLineBreak": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", "<", ">=", "<="],
"requireParenthesesAroundIIFE": true,
"requireSpaceAfterBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", "<", ">=", "<="],
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
"requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", "<", ">=", "<="],
"requireSpacesInConditionalExpression": true,
"requireSpacesInFunctionExpression": { "beforeOpeningCurlyBrace": true },
"requireSpacesInForStatement": true,
"validateIndentation": 4,
"validateLineBreaks": "LF",
"validateParameterSeparator": ", ",
"plugins": [
"jscs-jsdoc"
],
"jsDoc": {
"checkAnnotations": {
"preset": "jsdoc3"
},
"checkParamNames": true,
"checkRedundantParams": true,
"requireParamTypes": true
}
}

Просмотреть файл

@ -1,16 +0,0 @@
{
"bitwise": true,
"curly": true,
"eqeqeq": true,
"forin": true,
"futurehostile": true,
"immed": true,
"latedef": true,
"newcap": true,
"noarg": true,
"node": true,
"noempty": true,
"sub": true,
"undef": true,
"unused": "vars"
}

Просмотреть файл

@ -6,7 +6,7 @@ Please don't edit files in the `dist` subdirectory as they are generated via Gru
### Code style
The project's coding style is laid out in the JSHint, ESLint, and JSCS configurations.
The project's coding style is laid out in the ESLint configuration.
### PhantomJS

Просмотреть файл

@ -5,10 +5,13 @@
* Portions Copyright 2013-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootlint/blob/master/LICENSE)
*/
/*eslint-env node */
/* eslint-env node */
/* eslint indent: [2, 2] */
'use strict';
module.exports = function (grunt) {
'use strict';
// Force use of Unix newlines
grunt.util.linefeed = '\n';
@ -22,13 +25,11 @@ module.exports = function (grunt) {
grunt.initConfig({
// Metadata.
pkg: grunt.file.readJSON('package.json'),
banner: (
"/*!\n * Bootlint v<%= pkg.version %> (<%= pkg.homepage %>)\n" +
" * <%= pkg.description %>\n" +
" * Copyright (c) 2014-2016 The Bootlint Authors\n" +
" * Licensed under the MIT License (https://github.com/twbs/bootlint/blob/master/LICENSE).\n" +
" */\n"
),
banner: '/*!\n * Bootlint v<%= pkg.version %> (<%= pkg.homepage %>)\n' +
' * <%= pkg.description %>\n' +
' * Copyright (c) 2014-2016 The Bootlint Authors\n' +
' * Licensed under the MIT License (https://github.com/twbs/bootlint/blob/master/LICENSE).\n' +
' */\n',
// Task configuration.
browserify: {
dist: {
@ -53,9 +54,9 @@ module.exports = function (grunt) {
},
files: ['test/fixtures/**/*.html', '!test/fixtures/jquery/missing.html', '!test/fixtures/jquery/and_bs_js_both_missing.html', '!test/fixtures/charset/not-utf8.html']
},
jshint: {
eslint: {
options: {
jshintrc: '.jshintrc'
config: '.eslintrc'
},
web: {
src: ['app.js', 'bin/www']
@ -70,58 +71,24 @@ module.exports = function (grunt) {
src: ['test/**/*.js', '!test/lib/**/*.js']
}
},
jscs: {
web: {
src: '<%= jshint.web.src %>'
},
gruntfile: {
src: '<%= jshint.gruntfile.src %>',
options: {
validateIndentation: 2
}
},
lib: {
src: '<%= jshint.lib.src %>'
},
test: {
src: '<%= jshint.test.src %>'
}
},
eslint: {
options: {
config: '.eslintrc'
},
web: {
src: '<%= jshint.web.src %>'
},
gruntfile: {
src: '<%= jshint.gruntfile.src %>'
},
lib: {
src: '<%= jshint.lib.src %>'
},
test: {
src: '<%= jshint.test.src %>'
}
},
watch: {
gruntfile: {
files: '<%= jshint.gruntfile.src %>',
tasks: ['jshint:gruntfile']
files: '<%= eslint.gruntfile.src %>',
tasks: ['eslint:gruntfile']
},
lib: {
files: '<%= jshint.lib.src %>',
tasks: ['jshint:lib', 'nodeunit']
files: '<%= eslint.lib.src %>',
tasks: ['eslint:lib', 'nodeunit']
},
test: {
files: '<%= jshint.test.src %>',
tasks: ['jshint:test', 'nodeunit']
files: '<%= eslint.test.src %>',
tasks: ['eslint:test', 'nodeunit']
}
}
});
// Tasks
grunt.registerTask('lint', ['jshint', 'jscs', 'eslint']);
grunt.registerTask('lint', 'eslint');
grunt.registerTask('dist', ['browserify', 'usebanner']);
grunt.registerTask('test', ['lint', 'dist', 'nodeunit', 'qunit']);
grunt.registerTask('default', ['test']);

Просмотреть файл

@ -190,7 +190,7 @@ Response:
## Contributing
The project's coding style is laid out in the JSHint, ESLint, and JSCS configurations. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).
The project's coding style is laid out in the ESLint configuration. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).
_Also, please don't edit files in the "dist" subdirectory as they are generated via Grunt. You'll find source code in the "src" subdirectory!_

36
app.js
Просмотреть файл

@ -4,7 +4,7 @@ Run it via: npm run start
This is pretty niche. Most users should probably use the CLI or bookmarklet instead.
*/
/*eslint-env node */
/* eslint-env node */
'use strict';
@ -61,12 +61,15 @@ function lintsFor(html, disabledIds) {
}
/*eslint-disable new-cap */
/* eslint-disable new-cap */
var routes = express.Router();
/*eslint-enable new-cap */
/* eslint-enable new-cap */
routes.get('/', function (req, res) {
res.status(200).json({status: 200, message: 'Bootlint is online!'});
res.status(200).json({
status: 200,
message: 'Bootlint is online!'
});
});
routes.post('/', function (req, res) {
@ -74,7 +77,11 @@ routes.post('/', function (req, res) {
return req.is(type);
});
if (!isHtml) {
res.status(415).json({status: 415, message: 'Unsupported Media Type', details: 'Content-Type was not an HTML MIME type'});
res.status(415).json({
status: 415,
message: 'Unsupported Media Type',
details: 'Content-Type was not an HTML MIME type'
});
return;
}
@ -82,12 +89,16 @@ routes.post('/', function (req, res) {
'application/json': function () {
var disabledIds = disabledIdsFor(req);
var html = req.body;
// console.log("HTML: ", html);
// console.log('HTML: ', html);
var lints = lintsFor(html, disabledIds);
res.status(200).json(lints);
},
'default': function () {
res.status(406).json({status: 406, message: 'Not Acceptable', details: '"Accept" header must allow MIME type application/json'});
res.status(406).json({
status: 406,
message: 'Not Acceptable',
details: '"Accept" header must allow MIME type application/json'
});
}
});
});
@ -97,7 +108,10 @@ var app = express();
app.use(logger('dev'));
HTML_MIME_TYPES.forEach(function (type) {
app.use(bodyParser.text({type: type, limit: MAX_HTML_SIZE}));
app.use(bodyParser.text({
type: type,
limit: MAX_HTML_SIZE
}));
});
app.use('/', routes);
@ -114,9 +128,9 @@ app.use(function (req, res, next) {
// development error handler
// will print stacktrace
/*eslint-disable no-unused-vars */
/* eslint-disable no-unused-vars */
app.use(function (err, req, res, next) {
var isHttpErr = !!err.status;
var isHttpErr = Boolean(err.status);
if (!isHttpErr) {
err.status = 500;
@ -132,7 +146,7 @@ app.use(function (err, req, res, next) {
res.status(err.status).json(errJson);
});
/*eslint-enable no-unused-vars */
/* eslint-enable no-unused-vars */
module.exports = app;

Просмотреть файл

@ -1,6 +1,6 @@
#!/usr/bin/env node
/*eslint-env node */
/*eslint no-process-env: 0 */
/* eslint-env node */
/* eslint no-process-env: 0 */
'use strict';
require('debug')('bootlint');

2089
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -53,15 +53,12 @@
"grunt-banner": "^0.6.0",
"grunt-browserify": "^5.0.0",
"grunt-contrib-clean": "^1.0.0",
"grunt-contrib-jshint": "^1.0.0",
"grunt-contrib-nodeunit": "^1.0.0",
"grunt-contrib-qunit": "^1.0.1",
"grunt-contrib-watch": "^1.0.0",
"grunt-eslint": "^18.0.0",
"grunt-jscs": "^2.8.0",
"grunt-eslint": "^20.0.0",
"jquery": "^3.1.0",
"jscoverage": "^0.6.0",
"jscs-jsdoc": "^1.3.1",
"load-grunt-tasks": "^3.1.0",
"nodeunit": "^0.9.0",
"rewire": "^2.3.1",

Просмотреть файл

@ -5,7 +5,7 @@
* Licensed under the MIT License.
*/
/*eslint-env node */
/* eslint-env node */
var cheerio = require('cheerio');
var parseUrl = require('url').parse;
@ -27,14 +27,14 @@ var LocationIndex = _location.LocationIndex;
}
});
var SCREEN2NUM = {
'xs': 0,
'sm': 1,
'md': 2,
'lg': 3
xs: 0,
sm: 1,
md: 2,
lg: 3
};
var NUM2SCREEN = ['xs', 'sm', 'md', 'lg'];
var IN_NODE_JS = !!(cheerio.load);
var MIN_JQUERY_VERSION = '1.9.1';// as of Bootstrap v3.3.0
var IN_NODE_JS = Boolean(cheerio.load);
var MIN_JQUERY_VERSION = '1.9.1'; // as of Bootstrap v3.3.0
var CURRENT_BOOTSTRAP_VERSION = '3.3.7';
var BOOTSTRAP_VERSION_4 = '4.0.0';
var PLUGINS = [
@ -170,8 +170,7 @@ var LocationIndex = _location.LocationIndex;
if (start === null) {
// first element starts a trivial run
start = current;
}
else if (prev + 1 !== current) {
} else if (prev + 1 !== current) {
// run ended
if (start !== prev) {
// run is nontrivial
@ -188,17 +187,15 @@ var LocationIndex = _location.LocationIndex;
}
/**
* This function returns the browser window object, or null if this is not running in a browser environment.
* @returns {(Window|null)}
* @returns {(Window|null)} The browser window object, or null if this is not running in a browser environment
*/
function getBrowserWindowObject() {
var theWindow = null;
try {
/*eslint-disable no-undef, block-scoped-var */
theWindow = window;// jshint ignore:line
/*eslint-enable no-undef, block-scoped-var */
}
catch (e) {
/* eslint-disable no-undef, block-scoped-var */
theWindow = window;
/* eslint-enable no-undef, block-scoped-var */
} catch (e) {
// deliberately do nothing
// empty
}
@ -217,7 +214,7 @@ var LocationIndex = _location.LocationIndex;
function versionInLinkedElement($, element) {
var elem = $(element);
var urlAttr = (tagNameOf(element) === 'LINK') ? 'href' : 'src';
var urlAttr = tagNameOf(element) === 'LINK' ? 'href' : 'src';
var pathSegments = parseUrl(elem.attr(urlAttr)).pathname.split('/');
var versions = versionsIn(pathSegments);
if (!versions.length) {
@ -248,12 +245,12 @@ var LocationIndex = _location.LocationIndex;
var longhands = $('script[src*="bootstrap.js"]').filter(function (i, script) {
var url = $(script).attr('src');
var filename = filenameFromUrl(url);
return filename === "bootstrap.js";
return filename === 'bootstrap.js';
});
var minifieds = $('script[src*="bootstrap.min.js"]').filter(function (i, script) {
var url = $(script).attr('src');
var filename = filenameFromUrl(url);
return filename === "bootstrap.min.js";
return filename === 'bootstrap.min.js';
});
return {
@ -294,19 +291,17 @@ var LocationIndex = _location.LocationIndex;
function addLinter(id, linter) {
if (allLinters[id]) {
/* @covignore */
throw new Error("Linter already registered with ID: " + id);
throw new Error('Linter already registered with ID: ' + id);
}
var Problem = null;
if (id[0] === 'E') {
Problem = LintError;
}
else if (id[0] === 'W') {
} else if (id[0] === 'W') {
Problem = LintWarning;
}
else {
} else {
/* @covignore */
throw new Error("Invalid linter ID: " + id);
throw new Error('Invalid linter ID: ' + id);
}
function linterWrapper($, reporter) {
@ -322,7 +317,7 @@ var LocationIndex = _location.LocationIndex;
}
addLinter("W001", function lintMetaCharsetUtf8($, reporter) {
addLinter('W001', function lintMetaCharsetUtf8($, reporter) {
var meta = $('head>meta[charset]');
var charset = meta.attr('charset');
if (!charset) {
@ -330,50 +325,48 @@ var LocationIndex = _location.LocationIndex;
'head>meta[http-equiv="Content-Type"][content="text/html; charset=utf-8"]',
'head>meta[http-equiv="content-type"][content="text/html; charset=utf-8"]',
'head>meta[http-equiv="Content-Type"][content="text/html; charset=UTF-8"]',
'head>meta[http-equiv="content-type"][content="text/html; charset=UTF-8"]',
'head>meta[http-equiv="content-type"][content="text/html; charset=UTF-8"]'
].join(','));
if (!meta.length) {
reporter('`<head>` is missing UTF-8 charset `<meta>` tag');
}
}
else if (charset.toLowerCase() !== "utf-8") {
} else if (charset.toLowerCase() !== 'utf-8') {
reporter('charset `<meta>` tag is specifying a legacy, non-UTF-8 charset', meta);
}
});
addLinter("W002", function lintXUaCompatible($, reporter) {
addLinter('W002', function lintXUaCompatible($, reporter) {
var meta = $([
'head>meta[http-equiv="X-UA-Compatible"][content="IE=edge"]',
'head>meta[http-equiv="x-ua-compatible"][content="ie=edge"]'
].join(','));
if (!meta.length) {
reporter("`<head>` is missing X-UA-Compatible `<meta>` tag that disables old IE compatibility modes");
reporter('`<head>` is missing X-UA-Compatible `<meta>` tag that disables old IE compatibility modes');
}
});
addLinter("W003", function lintViewport($, reporter) {
addLinter('W003', function lintViewport($, reporter) {
var meta = $('head>meta[name="viewport"][content]');
if (!meta.length) {
reporter("`<head>` is missing viewport `<meta>` tag that enables responsiveness");
reporter('`<head>` is missing viewport `<meta>` tag that enables responsiveness');
}
});
addLinter("W004", function lintRemoteModals($, reporter) {
addLinter('W004', function lintRemoteModals($, reporter) {
var remoteModalTriggers = $('[data-toggle="modal"][data-remote]');
if (remoteModalTriggers.length) {
reporter("Found one or more modals using the deprecated `remote` option", remoteModalTriggers);
reporter('Found one or more modals using the deprecated `remote` option', remoteModalTriggers);
}
});
addLinter("W005", function lintJquery($, reporter) {
var OLD_JQUERY = "Found what might be an outdated version of jQuery; Bootstrap requires jQuery v" + MIN_JQUERY_VERSION + " or higher";
var NO_JQUERY_BUT_BS_JS = "Unable to locate jQuery, which is required for Bootstrap's JavaScript plugins to work";
var NO_JQUERY_NOR_BS_JS = "Unable to locate jQuery, which is required for Bootstrap's JavaScript plugins to work; however, you might not be using Bootstrap's JavaScript";
addLinter('W005', function lintJquery($, reporter) {
var OLD_JQUERY = 'Found what might be an outdated version of jQuery; Bootstrap requires jQuery v' + MIN_JQUERY_VERSION + ' or higher';
var NO_JQUERY_BUT_BS_JS = 'Unable to locate jQuery, which is required for Bootstrap\'s JavaScript plugins to work';
var NO_JQUERY_NOR_BS_JS = 'Unable to locate jQuery, which is required for Bootstrap\'s JavaScript plugins to work; however, you might not be using Bootstrap\'s JavaScript';
var bsScripts = bootstrapScriptsIn($);
var hasBsJs = !!(bsScripts.minifieds.length || bsScripts.longhands.length);
var hasBsJs = Boolean(bsScripts.minifieds.length || bsScripts.longhands.length);
var theWindow = null;
try {
/*eslint-disable no-undef, block-scoped-var */
theWindow = window;// jshint ignore:line
/*eslint-enable no-undef, block-scoped-var */
}
catch (e) {
/* eslint-disable no-undef, block-scoped-var */
theWindow = window;
/* eslint-enable no-undef, block-scoped-var */
} catch (e) {
// deliberately do nothing
// empty
}
@ -385,22 +378,20 @@ var LocationIndex = _location.LocationIndex;
var globalVersion = null;
try {
globalVersion = globaljQuery.fn.jquery.split(' ')[0];
}
catch (e) {
} catch (e) {
// skip; not actually jQuery?
// empty
}
if (globalVersion) {
// pad out short version numbers (e.g. '1.7')
while (globalVersion.match(/\./g).length < 2) {
globalVersion += ".0";
globalVersion += '.0';
}
var upToDate = null;
try {
upToDate = semver.gte(globalVersion, MIN_JQUERY_VERSION, true);
}
catch (e) {
} catch (e) {
// invalid version number
// empty
}
@ -440,7 +431,7 @@ var LocationIndex = _location.LocationIndex;
}
});
});
addLinter("W006", function lintTooltipsOnDisabledElems($, reporter) {
addLinter('W006', function lintTooltipsOnDisabledElems($, reporter) {
var selector = [
'[disabled][data-toggle="tooltip"]',
'.disabled[data-toggle="tooltip"]',
@ -450,26 +441,26 @@ var LocationIndex = _location.LocationIndex;
var disabledWithTooltips = $(selector);
if (disabledWithTooltips.length) {
reporter(
"Tooltips and popovers on disabled elements cannot be triggered by user interaction unless the element becomes enabled." +
" To have tooltips and popovers be triggerable by the user even when their associated element is disabled," +
" put the disabled element inside a wrapper `<div>` and apply the tooltip or popover to the wrapper `<div>` instead.",
'Tooltips and popovers on disabled elements cannot be triggered by user interaction unless the element becomes enabled.' +
' To have tooltips and popovers be triggerable by the user even when their associated element is disabled,' +
' put the disabled element inside a wrapper `<div>` and apply the tooltip or popover to the wrapper `<div>` instead.',
disabledWithTooltips
);
}
});
addLinter("W007", function lintBtnType($, reporter) {
addLinter('W007', function lintBtnType($, reporter) {
var badBtnType = $('button:not([type="submit"], [type="reset"], [type="button"])');
if (badBtnType.length) {
reporter("Found one or more `<button>`s missing a `type` attribute.", badBtnType);
reporter('Found one or more `<button>`s missing a `type` attribute.', badBtnType);
}
});
addLinter("W008", function lintTooltipsInBtnGroups($, reporter) {
addLinter('W008', function lintTooltipsInBtnGroups($, reporter) {
var nonBodyContainers = $('.btn-group [data-toggle="tooltip"]:not([data-container="body"]), .btn-group [data-toggle="popover"]:not([data-container="body"])');
if (nonBodyContainers.length) {
reporter("Tooltips and popovers within button groups should have their `container` set to `'body'`. Found tooltips/popovers that might lack this setting.", nonBodyContainers);
reporter('Tooltips and popovers within button groups should have their `container` set to `\'body\'`. Found tooltips/popovers that might lack this setting.', nonBodyContainers);
}
});
addLinter("W009", function lintEmptySpacerCols($, reporter) {
addLinter('W009', function lintEmptySpacerCols($, reporter) {
var selector = COL_CLASSES.map(function (colClass) {
return colClass + ':not(:last-child)';
}).join(',');
@ -478,8 +469,8 @@ var LocationIndex = _location.LocationIndex;
var column = $(col);
var isVoidElement = voidElements[col.tagName.toLowerCase()];
// can't just use :empty because :empty excludes nodes with all-whitespace text content
var hasText = !!column.text().trim().length;
var hasChildren = !!column.children(':first-child').length;
var hasText = Boolean(column.text().trim().length);
var hasChildren = Boolean(column.children(':first-child').length);
if (hasChildren || hasText || isVoidElement) {
return;
}
@ -492,16 +483,16 @@ var LocationIndex = _location.LocationIndex;
var colRegex = new RegExp('\\b(col-)(' + SCREENS.join('|') + ')(-\\d+)\\b', 'g');
var offsetClasses = colClasses.replace(colRegex, '$1$2-offset$3');
reporter("Using empty spacer columns isn't necessary with Bootstrap's grid. So instead of having an empty grid column with " + '`class="' + colClasses + '"` , just add `class="' + offsetClasses + '"` to the next grid column.', column);
reporter('Using empty spacer columns isn\'t necessary with Bootstrap\'s grid. So instead of having an empty grid column with `class="' + colClasses + '"` , just add `class="' + offsetClasses + '"` to the next grid column.', column);
});
});
addLinter("W010", function lintMediaPulls($, reporter) {
addLinter('W010', function lintMediaPulls($, reporter) {
var mediaPulls = $('.media>.pull-left, .media>.pull-right');
if (mediaPulls.length) {
reporter('Using `.pull-left` or `.pull-right` as part of the media object component is deprecated as of Bootstrap v3.3.0. Use `.media-left` or `.media-right` instead.', mediaPulls);
reporter('Using `.pull-left` or `.pull-right` as part of the media object component is deprecated as of Bootstrap v3.3.0. Use `.media-left` or `.media-right` instead.');
}
});
addLinter("W012", function lintNavbarContainers($, reporter) {
addLinter('W012', function lintNavbarContainers($, reporter) {
var navBars = $('.navbar');
var containers = [
'.container',
@ -509,15 +500,15 @@ var LocationIndex = _location.LocationIndex;
].join(',');
navBars.each(function () {
var navBar = $(this);
var hasContainerChildren = !!navBar.children(containers).length;
var hasContainerChildren = Boolean(navBar.children(containers).length);
if (!hasContainerChildren) {
reporter("`.navbar`'s first child element should always be either `.container` or `.container-fluid`", navBar);
reporter('`.navbar`\'s first child element should always be either `.container` or `.container-fluid`', navBar);
}
});
});
addLinter("W013", function lintOutdatedBootstrap($, reporter) {
var OUTDATED_BOOTSTRAP = "Bootstrap version might be outdated. Latest version is at least " + CURRENT_BOOTSTRAP_VERSION + " ; saw what appears to be usage of Bootstrap ";
addLinter('W013', function lintOutdatedBootstrap($, reporter) {
var OUTDATED_BOOTSTRAP = 'Bootstrap version might be outdated. Latest version is at least ' + CURRENT_BOOTSTRAP_VERSION + ' ; saw what appears to be usage of Bootstrap ';
var theWindow = getBrowserWindowObject();
var globaljQuery = theWindow && (theWindow.$ || theWindow.jQuery);
/* @covignore */
@ -543,7 +534,7 @@ var LocationIndex = _location.LocationIndex;
}
});
});
addLinter("W014", function lintCarouselControls($, reporter) {
addLinter('W014', function lintCarouselControls($, reporter) {
var controls = $('.carousel-indicators > li, .carousel-control');
controls.each(function (_index, cont) {
var control = $(cont);
@ -555,8 +546,8 @@ var LocationIndex = _location.LocationIndex;
}
});
});
addLinter("W015", function lintNewBootstrap($, reporter) {
var FUTURE_VERSION_ERROR = "Detected what appears to be Bootstrap v4 or later. This version of Bootlint only supports Bootstrap v3.";
addLinter('W015', function lintNewBootstrap($, reporter) {
var FUTURE_VERSION_ERROR = 'Detected what appears to be Bootstrap v4 or later. This version of Bootlint only supports Bootstrap v3.';
var theWindow = getBrowserWindowObject();
var globaljQuery = theWindow && (theWindow.$ || theWindow.jQuery);
@ -583,22 +574,22 @@ var LocationIndex = _location.LocationIndex;
}
});
});
addLinter("W016", function lintDisabledClassOnButton($, reporter) {
addLinter('W016', function lintDisabledClassOnButton($, reporter) {
var btnsWithDisabledClass = $('button.btn.disabled, input.btn.disabled');
if (btnsWithDisabledClass.length) {
reporter("Using the `.disabled` class on a `<button>` or `<input>` only changes the appearance of the element. It doesn't prevent the user from interacting with the element (for example, clicking on it or focusing it). If you want to truly disable the element, use the `disabled` attribute instead.", btnsWithDisabledClass);
reporter('Using the `.disabled` class on a `<button>` or `<input>` only changes the appearance of the element. It doesn\'t prevent the user from interacting with the element (for example, clicking on it or focusing it). If you want to truly disable the element, use the `disabled` attribute instead.', btnsWithDisabledClass);
}
});
addLinter("W017", function lintInputsMissingTypeAttr($, reporter) {
addLinter('W017', function lintInputsMissingTypeAttr($, reporter) {
var inputsMissingTypeAttr = $('input:not([type])');
if (inputsMissingTypeAttr.length) {
reporter("Found one or more `<input>`s missing a `type` attribute.", inputsMissingTypeAttr);
reporter('Found one or more `<input>`s missing a `type` attribute.', inputsMissingTypeAttr);
}
});
addLinter("E001", (function () {
var MISSING_DOCTYPE = "Document is missing a DOCTYPE declaration";
var NON_HTML5_DOCTYPE = "Document declares a non-HTML5 DOCTYPE";
addLinter('E001', (function () {
var MISSING_DOCTYPE = 'Document is missing a DOCTYPE declaration';
var NON_HTML5_DOCTYPE = 'Document declares a non-HTML5 DOCTYPE';
if (IN_NODE_JS) {
return function lintDoctype($, reporter) {
var doctype = $(':root')[0];
@ -615,25 +606,23 @@ var LocationIndex = _location.LocationIndex;
}
};
}
else {
/* @covignore */
return function lintDoctype($, reporter) {
/*eslint-disable no-undef, block-scoped-var */
var doc = window.document;// jshint ignore:line
/*eslint-enable un-undef, block-scoped-var */
if (doc.doctype === null) {
reporter(MISSING_DOCTYPE);
}
else if (doc.doctype.publicId) {
reporter(NON_HTML5_DOCTYPE);
}
else if (doc.doctype.systemId && doc.doctype.systemId !== "about:legacy-compat") {
reporter(NON_HTML5_DOCTYPE);
}
};
}
/* @covignore */
return function lintDoctype($, reporter) {
/* eslint-disable no-undef, block-scoped-var */
var doc = window.document;
/* eslint-enable un-undef, block-scoped-var */
if (doc.doctype === null) {
reporter(MISSING_DOCTYPE);
} else if (doc.doctype.publicId) {
reporter(NON_HTML5_DOCTYPE);
} else if (doc.doctype.systemId && doc.doctype.systemId !== 'about:legacy-compat') {
reporter(NON_HTML5_DOCTYPE);
}
};
})());
addLinter("E002", function lintBootstrapv2($, reporter) {
addLinter('E002', function lintBootstrapv2($, reporter) {
var columnClasses = [];
for (var n = 1; n <= 12; n++) {
columnClasses.push('.span' + n);
@ -641,10 +630,10 @@ var LocationIndex = _location.LocationIndex;
var selector = columnClasses.join(',');
var spanNs = $(selector);
if (spanNs.length) {
reporter("Found one or more uses of outdated Bootstrap v2 `.spanN` grid classes", spanNs);
reporter('Found one or more uses of outdated Bootstrap v2 `.spanN` grid classes', spanNs);
}
});
addLinter("E003", function lintContainers($, reporter) {
addLinter('E003', function lintContainers($, reporter) {
var notAnyColClass = COL_CLASSES.map(function (colClass) {
return ':not(' + colClass + ')';
}).join('');
@ -661,42 +650,42 @@ var LocationIndex = _location.LocationIndex;
return true;
});
if (rowsOutsideColumnsAndContainers.length) {
reporter("Found one or more `.row`s that were not children of a grid column or descendants of a `.container` or `.container-fluid`", rowsOutsideColumnsAndContainers);
reporter('Found one or more `.row`s that were not children of a grid column or descendants of a `.container` or `.container-fluid`', rowsOutsideColumnsAndContainers);
}
});
addLinter("E004", function lintNestedContainers($, reporter) {
addLinter('E004', function lintNestedContainers($, reporter) {
var nestedContainers = $('.container, .container-fluid').children('.container, .container-fluid');
if (nestedContainers.length) {
reporter("Containers (`.container` and `.container-fluid`) are not nestable", nestedContainers);
reporter('Containers (`.container` and `.container-fluid`) are not nestable', nestedContainers);
}
});
addLinter("E005", function lintRowAndColOnSameElem($, reporter) {
addLinter('E005', function lintRowAndColOnSameElem($, reporter) {
var selector = COL_CLASSES.map(function (col) {
return ".row" + col;
return '.row' + col;
}).join(',');
var rowCols = $(selector);
if (rowCols.length) {
reporter("Found both `.row` and `.col-*-*` used on the same element", rowCols);
reporter('Found both `.row` and `.col-*-*` used on the same element', rowCols);
}
});
addLinter("E006", function lintInputGroupFormControlTypes($, reporter) {
addLinter('E006', function lintInputGroupFormControlTypes($, reporter) {
var selectInputGroups = $('.input-group select');
if (selectInputGroups.length) {
reporter("`.input-group` contains a `<select>`; this should be avoided as `<select>`s cannot be fully styled in WebKit browsers", selectInputGroups);
reporter('`.input-group` contains a `<select>`; this should be avoided as `<select>`s cannot be fully styled in WebKit browsers', selectInputGroups);
}
var textareaInputGroups = $('.input-group textarea');
if (textareaInputGroups.length) {
reporter("`.input-group` contains a `<textarea>`; only text-based `<input>`s are permitted in an `.input-group`", textareaInputGroups);
reporter('`.input-group` contains a `<textarea>`; only text-based `<input>`s are permitted in an `.input-group`', textareaInputGroups);
}
});
addLinter("E007", function lintBootstrapJs($, reporter) {
addLinter('E007', function lintBootstrapJs($, reporter) {
var scripts = bootstrapScriptsIn($);
if (scripts.longhands.length && scripts.minifieds.length) {
reporter("Only one copy of Bootstrap's JS should be included; currently the webpage includes both bootstrap.js and bootstrap.min.js", scripts.longhands.add(scripts.minifieds));
reporter('Only one copy of Bootstrap\'s JS should be included; currently the webpage includes both bootstrap.js and bootstrap.min.js', scripts.longhands.add(scripts.minifieds));
}
});
addLinter("E009", function lintMissingInputGroupSizes($, reporter) {
addLinter('E009', function lintMissingInputGroupSizes($, reporter) {
var selector = [
'.input-group:not(.input-group-lg) .btn-lg',
'.input-group:not(.input-group-lg) .input-lg',
@ -705,34 +694,34 @@ var LocationIndex = _location.LocationIndex;
].join(',');
var badInputGroupSizing = $(selector);
if (badInputGroupSizing.length) {
reporter("Button and input sizing within `.input-group`s can cause issues. Instead, use input group sizing classes `.input-group-lg` or `.input-group-sm`", badInputGroupSizing);
reporter('Button and input sizing within `.input-group`s can cause issues. Instead, use input group sizing classes `.input-group-lg` or `.input-group-sm`', badInputGroupSizing);
}
});
addLinter("E010", function lintMultipleFormControlsInInputGroup($, reporter) {
addLinter('E010', function lintMultipleFormControlsInInputGroup($, reporter) {
var badInputGroups = $('.input-group').filter(function (i, inputGroup) {
return $(inputGroup).find('.form-control').length > 1;
});
if (badInputGroups.length) {
reporter("Input groups cannot contain multiple `.form-control`s", badInputGroups);
reporter('Input groups cannot contain multiple `.form-control`s', badInputGroups);
}
});
addLinter("E011", function lintFormGroupMixedWithInputGroup($, reporter) {
addLinter('E011', function lintFormGroupMixedWithInputGroup($, reporter) {
var badMixes = $('.input-group.form-group');
if (badMixes.length) {
reporter("`.input-group` and `.form-group` cannot be used directly on the same element. Instead, nest the `.input-group` within the `.form-group`", badMixes);
reporter('`.input-group` and `.form-group` cannot be used directly on the same element. Instead, nest the `.input-group` within the `.form-group`', badMixes);
}
});
addLinter("E012", function lintGridClassMixedWithInputGroup($, reporter) {
addLinter('E012', function lintGridClassMixedWithInputGroup($, reporter) {
var selector = COL_CLASSES.map(function (colClass) {
return '.input-group' + colClass;
}).join(',');
var badMixes = $(selector);
if (badMixes.length) {
reporter("`.input-group` and `.col-*-*` cannot be used directly on the same element. Instead, nest the `.input-group` within the `.col-*-*`", badMixes);
reporter('`.input-group` and `.col-*-*` cannot be used directly on the same element. Instead, nest the `.input-group` within the `.col-*-*`', badMixes);
}
});
addLinter("E013", function lintRowChildrenAreCols($, reporter) {
addLinter('E013', function lintRowChildrenAreCols($, reporter) {
var ALLOWED_CHILDREN = COL_CLASSES.concat(['script', '.clearfix', '.bs-customizer-input']);
var selector = '.row>*' + ALLOWED_CHILDREN.map(function (colClass) {
return ':not(' + colClass + ')';
@ -740,20 +729,20 @@ var LocationIndex = _location.LocationIndex;
var nonColRowChildren = $(selector);
if (nonColRowChildren.length) {
reporter("Only columns (`.col-*-*`) may be children of `.row`s", nonColRowChildren);
reporter('Only columns (`.col-*-*`) may be children of `.row`s', nonColRowChildren);
}
});
addLinter("E014", function lintColParentsAreRowsOrFormGroups($, reporter) {
addLinter('E014', function lintColParentsAreRowsOrFormGroups($, reporter) {
var selector = COL_CLASSES.map(function (colClass) {
return '*:not(.row):not(.form-group)>' + colClass + ':not(col):not(th):not(td)';
}).join(',');
var colsOutsideRowsAndFormGroups = $(selector);
if (colsOutsideRowsAndFormGroups.length) {
reporter("Columns (`.col-*-*`) can only be children of `.row`s or `.form-group`s", colsOutsideRowsAndFormGroups);
reporter('Columns (`.col-*-*`) can only be children of `.row`s or `.form-group`s', colsOutsideRowsAndFormGroups);
}
});
addLinter("E015", function lintInputGroupsWithMultipleAddOnsPerSide($, reporter) {
addLinter('E015', function lintInputGroupsWithMultipleAddOnsPerSide($, reporter) {
var addOnClasses = ['.input-group-addon', '.input-group-btn'];
var combos = [];
addOnClasses.forEach(function (first) {
@ -764,16 +753,16 @@ var LocationIndex = _location.LocationIndex;
var selector = combos.join(',');
var multipleAddOns = $(selector);
if (multipleAddOns.length) {
reporter("Having multiple add-ons on a single side of an input group is not supported", multipleAddOns);
reporter('Having multiple add-ons on a single side of an input group is not supported', multipleAddOns);
}
});
addLinter("E016", function lintBtnToggle($, reporter) {
addLinter('E016', function lintBtnToggle($, reporter) {
var badBtnToggle = $('.btn.dropdown-toggle ~ .btn');
if (badBtnToggle.length) {
reporter("`.btn.dropdown-toggle` must be the last button in a button group.", badBtnToggle);
reporter('`.btn.dropdown-toggle` must be the last button in a button group.', badBtnToggle);
}
});
addLinter("E017", function lintBlockCheckboxes($, reporter) {
addLinter('E017', function lintBlockCheckboxes($, reporter) {
var badCheckboxes = $('.checkbox').filter(function (i, div) {
return $(div).filter(':has(>label>input[type="checkbox"])').length <= 0;
});
@ -781,7 +770,7 @@ var LocationIndex = _location.LocationIndex;
reporter('Incorrect markup used with the `.checkbox` class. The correct markup structure is `.checkbox>label>input[type="checkbox"]`', badCheckboxes);
}
});
addLinter("E018", function lintBlockRadios($, reporter) {
addLinter('E018', function lintBlockRadios($, reporter) {
var badRadios = $('.radio').filter(function (i, div) {
return $(div).filter(':has(>label>input[type="radio"])').length <= 0;
});
@ -789,10 +778,10 @@ var LocationIndex = _location.LocationIndex;
reporter('Incorrect markup used with the `.radio` class. The correct markup structure is `.radio>label>input[type="radio"]`', badRadios);
}
});
addLinter("E019", function lintInlineCheckboxes($, reporter) {
addLinter('E019', function lintInlineCheckboxes($, reporter) {
var wrongElems = $('.checkbox-inline:not(label)');
if (wrongElems.length) {
reporter("`.checkbox-inline` should only be used on `<label>` elements", wrongElems);
reporter('`.checkbox-inline` should only be used on `<label>` elements', wrongElems);
}
var badStructures = $('.checkbox-inline').filter(function (i, label) {
return $(label).children('input[type="checkbox"]').length <= 0;
@ -801,10 +790,10 @@ var LocationIndex = _location.LocationIndex;
reporter('Incorrect markup used with the `.checkbox-inline` class. The correct markup structure is `label.checkbox-inline>input[type="checkbox"]`', badStructures);
}
});
addLinter("E020", function lintInlineRadios($, reporter) {
addLinter('E020', function lintInlineRadios($, reporter) {
var wrongElems = $('.radio-inline:not(label)');
if (wrongElems.length) {
reporter("`.radio-inline` should only be used on `<label>` elements", wrongElems);
reporter('`.radio-inline` should only be used on `<label>` elements', wrongElems);
}
var badStructures = $('.radio-inline').filter(function (i, label) {
return $(label).children('input[type="radio"]').length <= 0;
@ -813,7 +802,7 @@ var LocationIndex = _location.LocationIndex;
reporter('Incorrect markup used with the `.radio-inline` class. The correct markup structure is `label.radio-inline>input[type="radio"]`', badStructures);
}
});
addLinter("E021", function lintButtonsCheckedActive($, reporter) {
addLinter('E021', function lintButtonsCheckedActive($, reporter) {
var selector = [
'[data-toggle="buttons"]>label:not(.active)>input[type="checkbox"][checked]',
'[data-toggle="buttons"]>label.active>input[type="checkbox"]:not([checked])',
@ -822,58 +811,58 @@ var LocationIndex = _location.LocationIndex;
].join(',');
var mismatchedButtonInputs = $(selector);
if (mismatchedButtonInputs.length) {
reporter("`.active` class used without the `checked` attribute (or vice-versa) in a button group using the button.js plugin", mismatchedButtonInputs);
reporter('`.active` class used without the `checked` attribute (or vice-versa) in a button group using the button.js plugin', mismatchedButtonInputs);
}
});
addLinter("E022", function lintModalsWithinOtherComponents($, reporter) {
addLinter('E022', function lintModalsWithinOtherComponents($, reporter) {
var selector = [
'.table .modal',
'.navbar .modal'
'.table .modal',
'.navbar .modal'
].join(',');
var badNestings = $(selector);
if (badNestings.length) {
reporter("Modal markup should not be placed within other components, so as to avoid the component's styles interfering with the modal's appearance or functionality", badNestings);
reporter('Modal markup should not be placed within other components, so as to avoid the component\'s styles interfering with the modal\'s appearance or functionality', badNestings);
}
});
addLinter("E023", function lintPanelBodyWithoutPanel($, reporter) {
addLinter('E023', function lintPanelBodyWithoutPanel($, reporter) {
var badPanelBody = $('.panel-body').parent(':not(.panel, .panel-collapse)');
if (badPanelBody.length) {
reporter("`.panel-body` must have a `.panel` or `.panel-collapse` parent", badPanelBody);
reporter('`.panel-body` must have a `.panel` or `.panel-collapse` parent', badPanelBody);
}
});
addLinter("E024", function lintPanelHeadingWithoutPanel($, reporter) {
addLinter('E024', function lintPanelHeadingWithoutPanel($, reporter) {
var badPanelHeading = $('.panel-heading').parent(':not(.panel)');
if (badPanelHeading.length) {
reporter("`.panel-heading` must have a `.panel` parent", badPanelHeading);
reporter('`.panel-heading` must have a `.panel` parent', badPanelHeading);
}
});
addLinter("E025", function lintPanelFooterWithoutPanel($, reporter) {
addLinter('E025', function lintPanelFooterWithoutPanel($, reporter) {
var badPanelFooter = $('.panel-footer').parent(':not(.panel, .panel-collapse)');
if (badPanelFooter.length) {
reporter("`.panel-footer` must have a `.panel` or `.panel-collapse` parent", badPanelFooter);
reporter('`.panel-footer` must have a `.panel` or `.panel-collapse` parent', badPanelFooter);
}
});
addLinter("E026", function lintPanelTitleWithoutPanelHeading($, reporter) {
addLinter('E026', function lintPanelTitleWithoutPanelHeading($, reporter) {
var badPanelTitle = $('.panel-title').parent(':not(.panel-heading)');
if (badPanelTitle.length) {
reporter("`.panel-title` must have a `.panel-heading` parent", badPanelTitle);
reporter('`.panel-title` must have a `.panel-heading` parent', badPanelTitle);
}
});
addLinter("E027", function lintTableResponsive($, reporter) {
addLinter('E027', function lintTableResponsive($, reporter) {
var badStructure = $('.table.table-responsive, table.table-responsive');
if (badStructure.length) {
reporter("`.table-responsive` is supposed to be used on the table's parent wrapper `<div>`, not on the table itself", badStructure);
reporter('`.table-responsive` is supposed to be used on the table\'s parent wrapper `<div>`, not on the table itself', badStructure);
}
});
addLinter("E028", function lintFormControlFeedbackWithoutHasFeedback($, reporter) {
addLinter('E028', function lintFormControlFeedbackWithoutHasFeedback($, reporter) {
var ancestorsMissingClasses = $('.form-control-feedback').filter(function () {
return $(this).closest('.form-group.has-feedback').length !== 1;
});
if (ancestorsMissingClasses.length) {
reporter("`.form-control-feedback` must have a `.form-group.has-feedback` ancestor", ancestorsMissingClasses);
reporter('`.form-control-feedback` must have a `.form-group.has-feedback` ancestor', ancestorsMissingClasses);
}
});
addLinter("E029", function lintRedundantColumnClasses($, reporter) {
addLinter('E029', function lintRedundantColumnClasses($, reporter) {
var columns = $(COL_CLASSES.join(','));
columns.each(function (_index, col) {
var column = $(col);
@ -914,115 +903,115 @@ var LocationIndex = _location.LocationIndex;
var oldClass = '`class="' + classes + '"`';
var newClass = '`class="' + simplifiedClasses + '"`';
reporter(
"Since grid classes apply to devices with screen widths greater than or equal to the breakpoint sizes (unless overridden by grid classes targeting larger screens), " +
oldClass + " is redundant and can be simplified to " + newClass,
'Since grid classes apply to devices with screen widths greater than or equal to the breakpoint sizes (unless overridden by grid classes targeting larger screens), ' +
oldClass + ' is redundant and can be simplified to ' + newClass,
column
);
});
});
addLinter("E030", function lintSoloGlyphiconClasses($, reporter) {
addLinter('E030', function lintSoloGlyphiconClasses($, reporter) {
var missingGlyphiconClass = $('[class*="glyphicon-"]:not(.glyphicon):not(.glyphicon-class)').filter(function () {
return /\bglyphicon-([a-zA-Z]+)\b/.test($(this).attr('class'));
});
if (missingGlyphiconClass.length) {
reporter("Found elements with a `.glyphicon-*` class that were missing the additional required `.glyphicon` class.", missingGlyphiconClass);
reporter('Found elements with a `.glyphicon-*` class that were missing the additional required `.glyphicon` class.', missingGlyphiconClass);
}
});
addLinter("E031", function lintGlyphiconOnNonEmptyElement($, reporter) {
addLinter('E031', function lintGlyphiconOnNonEmptyElement($, reporter) {
var glyphiconNotEmpty = $('.glyphicon:not(:empty)');
if (glyphiconNotEmpty.length) {
reporter("Glyphicon classes must only be used on elements that contain no text content and have no child elements.", glyphiconNotEmpty);
reporter('Glyphicon classes must only be used on elements that contain no text content and have no child elements.', glyphiconNotEmpty);
}
});
addLinter("E032", function lintModalStructure($, reporter) {
addLinter('E032', function lintModalStructure($, reporter) {
var elements;
elements = $('.modal-dialog').parent(':not(.modal)');
if (elements.length) {
reporter("`.modal-dialog` must be a child of `.modal`", elements);
reporter('`.modal-dialog` must be a child of `.modal`', elements);
}
elements = $('.modal-content').parent(':not(.modal-dialog)');
if (elements.length) {
reporter("`.modal-content` must be a child of `.modal-dialog`", elements);
reporter('`.modal-content` must be a child of `.modal-dialog`', elements);
}
elements = $('.modal-header').parent(':not(.modal-content)');
if (elements.length) {
reporter("`.modal-header` must be a child of `.modal-content`", elements);
reporter('`.modal-header` must be a child of `.modal-content`', elements);
}
elements = $('.modal-body').parent(':not(.modal-content)');
if (elements.length) {
reporter("`.modal-body` must be a child of `.modal-content`", elements);
reporter('`.modal-body` must be a child of `.modal-content`', elements);
}
elements = $('.modal-footer').parent(':not(.modal-content)');
if (elements.length) {
reporter("`.modal-footer` must be a child of `.modal-content`", elements);
reporter('`.modal-footer` must be a child of `.modal-content`', elements);
}
elements = $('.modal-title').parent(':not(.modal-header)');
if (elements.length) {
reporter("`.modal-title` must be a child of `.modal-header`", elements);
reporter('`.modal-title` must be a child of `.modal-header`', elements);
}
});
addLinter("E033", function lintAlertMissingDismissible($, reporter) {
addLinter('E033', function lintAlertMissingDismissible($, reporter) {
var alertsMissingDismissible = $('.alert:not(.alert-dismissible):has([data-dismiss="alert"])');
if (alertsMissingDismissible.length) {
reporter('`.alert` with dismiss button must have class `.alert-dismissible`', alertsMissingDismissible);
}
});
addLinter("E034", function lintAlertDismissStructure($, reporter) {
addLinter('E034', function lintAlertDismissStructure($, reporter) {
var nonFirstChildCloses = $('.alert>.close:not(:first-child)');
var closesPrecededByText = $('.alert>.close').filter(function () {
var firstNode = $(this).parent().contents().eq(0);
var firstNodeIsText = IN_NODE_JS ? firstNode[0].type === 'text' : firstNode[0].nodeType === 3;
return !!(firstNodeIsText && firstNode.text().trim());
return Boolean(firstNodeIsText && firstNode.text().trim());
});
var problematicCloses = nonFirstChildCloses.add(closesPrecededByText);
if (problematicCloses.length) {
reporter('`.close` button for `.alert` must be the first element in the `.alert`', problematicCloses);
}
});
addLinter("E035", function lintFormGroupWithFormClass($, reporter) {
addLinter('E035', function lintFormGroupWithFormClass($, reporter) {
var badFormGroups = $('.form-group.form-inline, .form-group.form-horizontal');
if (badFormGroups.length) {
reporter('Neither `.form-inline` nor `.form-horizontal` should be used directly on a `.form-group`. Instead, nest the `.form-group` within the `.form-inline` or `.form-horizontal`', badFormGroups);
}
});
addLinter("E037", function lintColZeros($, reporter) {
addLinter('E037', function lintColZeros($, reporter) {
var selector = SCREENS.map(function (screen) {
return ".col-" + screen + "-0";
return '.col-' + screen + '-0';
}).join(',');
var elements = $(selector);
if (elements.length) {
reporter("Column widths must be positive integers (and <= 12 by default). Found usage(s) of invalid nonexistent `.col-*-0` classes.", elements);
reporter('Column widths must be positive integers (and <= 12 by default). Found usage(s) of invalid nonexistent `.col-*-0` classes.', elements);
}
});
addLinter("E038", function lintMediaPulls($, reporter) {
addLinter('E038', function lintMediaPulls($, reporter) {
var mediaPullsOutsideMedia = $('.media-left, .media-right').filter(function () {
return !($(this).parent().closest('.media').length);
return !$(this).parent().closest('.media').length;
});
if (mediaPullsOutsideMedia.length) {
reporter('`.media-left` and `.media-right` should not be used outside of `.media` objects.', mediaPullsOutsideMedia);
}
});
addLinter("E039", function lintNavbarPulls($, reporter) {
addLinter('E039', function lintNavbarPulls($, reporter) {
var navbarPullsOutsideNavbars = $('.navbar-left, .navbar-right').filter(function () {
return !($(this).parent().closest('.navbar').length);
return !$(this).parent().closest('.navbar').length;
});
if (navbarPullsOutsideNavbars.length) {
reporter('`.navbar-left` and `.navbar-right` should not be used outside of navbars.', navbarPullsOutsideNavbars);
}
});
addLinter("E040", function lintModalHide($, reporter) {
addLinter('E040', function lintModalHide($, reporter) {
var modalsWithHide = $('.modal.hide');
if (modalsWithHide.length) {
reporter('`.hide` should not be used on `.modal` in Bootstrap v3.', modalsWithHide);
}
});
addLinter("E041", function lintCarouselStructure($, reporter) {
addLinter('E041', function lintCarouselStructure($, reporter) {
var carouselsWithWrongInners = $('.carousel').filter(function () {
return $(this).children('.carousel-inner').length !== 1;
});
@ -1037,42 +1026,41 @@ var LocationIndex = _location.LocationIndex;
reporter('`.carousel-inner` must have exactly one `.item.active` child.', innersWithWrongActiveItems);
}
});
addLinter("E042", function lintFormControlOnWrongControl($, reporter) {
addLinter('E042', function lintFormControlOnWrongControl($, reporter) {
var formControlsOnWrongTags = $('.form-control:not(input,textarea,select)');
if (formControlsOnWrongTags.length) {
reporter('`.form-control` should only be used on `<input>`s, `<textarea>`s, and `<select>`s.', formControlsOnWrongTags);
}
var formControlsOnWrongTypes = $('input.form-control:not(' + ([
'color',
'email',
'number',
'password',
'search',
'tel',
'text',
'url',
'datetime',
'datetime-local',
'date',
'month',
'week',
'time'
].map(function (type) {
return '[type="' + type + '"]';
}).join(',')
) + ')');
var formControlsOnWrongTypes = $('input.form-control:not(' + [
'color',
'email',
'number',
'password',
'search',
'tel',
'text',
'url',
'datetime',
'datetime-local',
'date',
'month',
'week',
'time'
].map(function (type) {
return '[type="' + type + '"]';
}).join(',') + ')');
if (formControlsOnWrongTypes.length) {
reporter('`.form-control` cannot be used on non-textual `<input>`s, such as those whose `type` is: `file`, `checkbox`, `radio`, `range`, `button`', formControlsOnWrongTypes);
}
});
addLinter("E043", function lintNavbarNavAnchorButtons($, reporter) {
addLinter('E043', function lintNavbarNavAnchorButtons($, reporter) {
var navbarNavAnchorBtns = $('.navbar-nav a.btn, .navbar-nav a.navbar-btn');
if (navbarNavAnchorBtns.length) {
reporter('Button classes (`.btn`, `.btn-*`, `.navbar-btn`) cannot be used on `<a>`s within `.navbar-nav`s.', navbarNavAnchorBtns);
}
});
addLinter("E044", function lintInputGroupAddonChildren($, reporter) {
addLinter('E044', function lintInputGroupAddonChildren($, reporter) {
var badInputGroups = $('.input-group').filter(function () {
var inputGroup = $(this);
return !inputGroup.children('.form-control').length || !inputGroup.children('.input-group-addon, .input-group-btn').length;
@ -1081,31 +1069,31 @@ var LocationIndex = _location.LocationIndex;
reporter('`.input-group` must have a `.form-control` and either an `.input-group-addon` or an `.input-group-btn`.', badInputGroups);
}
});
addLinter("E045", function lintImgResponsiveOnNonImgs($, reporter) {
addLinter('E045', function lintImgResponsiveOnNonImgs($, reporter) {
var imgResponsiveNotOnImg = $('.img-responsive:not(img)');
if (imgResponsiveNotOnImg.length) {
reporter('`.img-responsive` should only be used on `<img>`s', imgResponsiveNotOnImg);
}
});
addLinter("E046", function lintModalTabIndex($, reporter) {
addLinter('E046', function lintModalTabIndex($, reporter) {
var modalsWithoutTabindex = $('.modal:not([tabindex])');
if (modalsWithoutTabindex.length) {
reporter('`.modal` elements must have a `tabindex` attribute.', modalsWithoutTabindex);
}
});
addLinter("E047", function lintBtnElements($, reporter) {
addLinter('E047', function lintBtnElements($, reporter) {
var btns = $('.btn:not(a,button,input,label)');
if (btns.length) {
reporter('`.btn` should only be used on `<a>`, `<button>`, `<input>`, or `<label>` elements.', btns);
}
});
addLinter("E048", function lintModalRole($, reporter) {
addLinter('E048', function lintModalRole($, reporter) {
var modals = $('.modal:not([role="dialog"])');
if (modals.length) {
reporter('`.modal` must have a `role="dialog"` attribute.', modals);
}
});
addLinter("E049", function lintModalDialogRole($, reporter) {
addLinter('E049', function lintModalDialogRole($, reporter) {
var modalDialogs = $('.modal-dialog:not([role="document"])');
if (modalDialogs.length) {
reporter('`.modal-dialog` must have a `role="document"` attribute.', modalDialogs);
@ -1156,8 +1144,7 @@ var LocationIndex = _location.LocationIndex;
var $ = cheerio.load(html, {withStartIndices: true});
this._lint($, reporter, disabledIds, html);
};
}
else {
} else {
// jQuery; in-browser
/* @covignore */
(function () {
@ -1189,38 +1176,36 @@ var LocationIndex = _location.LocationIndex;
var seenLint = false;
var errorCount = 0;
var reporter = function (lint) {
var background = "background: #" + (lint.id[0] === "W" ? "f0ad4e" : "d9534f") + "; color: #ffffff;";
var background = 'background: #' + (lint.id[0] === 'W' ? 'f0ad4e' : 'd9534f') + '; color: #ffffff;';
if (!seenLint) {
if (alertOnFirstProblem) {
/*eslint-disable no-alert, no-undef, block-scoped-var */
window.alert("bootlint found errors in this document! See the JavaScript console for details.");// jshint ignore:line
/*eslint-enable no-alert, no-undef, block-scoped-var */
/* eslint-disable no-alert, no-undef, block-scoped-var */
window.alert('bootlint found errors in this document! See the JavaScript console for details.');
/* eslint-enable no-alert, no-undef, block-scoped-var */
}
seenLint = true;
}
if (!lint.elements.length) {
console.warn("bootlint: %c " + lint.id + " ", background, lint.message + " Documentation: " + lint.url);
}
else {
console.warn("bootlint: %c " + lint.id + " ", background, lint.message + " Documentation: " + lint.url, lint.elements);
if (lint.elements.length) {
console.warn('bootlint: %c ' + lint.id + ' ', background, lint.message + ' Documentation: ' + lint.url, lint.elements);
} else {
console.warn('bootlint: %c ' + lint.id + ' ', background, lint.message + ' Documentation: ' + lint.url);
}
errorCount++;
};
this.lintCurrentDocument(reporter, disabledIds);
if (errorCount > 0) {
console.info("bootlint: For details, look up the lint problem IDs in the Bootlint wiki: https://github.com/twbs/bootlint/wiki");
}
else if (alertIfNoProblems) {
/*eslint-disable no-alert, no-undef, block-scoped-var */
window.alert("bootlint found no errors in this document.");// jshint ignore:line
/*eslint-enable no-alert, no-undef, block-scoped-var */
console.info('bootlint: For details, look up the lint problem IDs in the Bootlint wiki: https://github.com/twbs/bootlint/wiki');
} else if (alertIfNoProblems) {
/* eslint-disable no-alert, no-undef, block-scoped-var */
window.alert('bootlint found no errors in this document.');
/* eslint-enable no-alert, no-undef, block-scoped-var */
}
};
/*eslint-disable no-undef, block-scoped-var */
window.bootlint = exports;// jshint ignore:line
/*eslint-enable no-undef, block-scoped-var */
/* eslint-disable no-undef, block-scoped-var */
window.bootlint = exports;
/* eslint-enable no-undef, block-scoped-var */
})();
}
})(typeof exports === 'object' && exports || this);

Просмотреть файл

@ -1,3 +1,3 @@
#!/usr/bin/env node
/*eslint-env node */
/* eslint-env node */
require('./cli')();

Просмотреть файл

@ -1,5 +1,5 @@
/*eslint-env node */
/*eslint no-process-exit: 0 */
/* eslint-env node */
/* eslint no-process-exit: 0 */
'use strict';
var Deferred = require('bluebird');
@ -26,25 +26,25 @@ module.exports = function () {
function buildReporter(origin) {
return function (lint) {
var lintId = (lint.id[0] === 'E') ? chalk.bgGreen.white(lint.id) : chalk.bgRed.white(lint.id);
var lintId = lint.id[0] === 'E' ? chalk.bgGreen.white(lint.id) : chalk.bgRed.white(lint.id);
var output = false;
if (lint.elements) {
lint.elements.each(function (_, element) {
var loc = element.startLocation;
console.log(origin + ":" + (loc.line + 1) + ":" + (loc.column + 1), lintId, lint.message);
console.log(origin + ':' + (loc.line + 1) + ':' + (loc.column + 1), lintId, lint.message);
totalErrCount++;
output = true;
});
}
if (!output) {
console.log(origin + ":", lintId, lint.message);
console.log(origin + ':', lintId, lint.message);
totalErrCount++;
}
};
}
function handleStdin() {
return new Deferred(function (resolve) {
return new Deferred(function (resolve) { // eslint-disable-line consistent-return
if (process.stdin.isTTY) {
return resolve();
}
@ -89,13 +89,13 @@ module.exports = function () {
});
Deferred.all(lintedFiles).then(function () {
console.log("");
console.log('');
if (totalErrCount > 0) {
console.log("For details, look up the lint problem IDs in the Bootlint wiki: https://github.com/twbs/bootlint/wiki");
console.log('For details, look up the lint problem IDs in the Bootlint wiki: https://github.com/twbs/bootlint/wiki');
}
console.log("" + totalErrCount + " lint error(s) found across " + totalFileCount + " file(s).");
console.log(String(totalErrCount) + ' lint error(s) found across ' + totalFileCount + ' file(s).');
if (totalErrCount) {
process.exit(1);

Просмотреть файл

@ -1,4 +1,4 @@
/*eslint-env node */
/* eslint-env node */
var binarySearch = require('binary-search');
(function () {
@ -39,7 +39,7 @@ var binarySearch = require('binary-search');
var nextLineIndex = 1;
var charIndex = 0;
while (charIndex < string.length) {
charIndex = string.indexOf("\n", charIndex);
charIndex = string.indexOf('\n', charIndex);
if (charIndex === -1) {
/* @covignore */
break;
@ -66,13 +66,12 @@ var binarySearch = require('binary-search');
var index = binarySearch(this._lineStartEndTriples, charIndex, function (bucket, needle) {
if (needle < bucket[1]) {
return 1;
}
else if (needle >= bucket[2]) {
} else if (needle >= bucket[2]) {
return -1;
}
else {
return 0;
}
return 0;
});
if (index < 0) { // binarySearch returns a negative number (but not necessarily -1) when match not found
/* @covignore */

Просмотреть файл

@ -1,5 +1,5 @@
/*eslint-env node, browser */
/* jshint browser: true */
/* eslint-env node, browser */
/**
* Simple lightweight shim of Node.js's `url.parse()`
* ( http://nodejs.org/docs/latest/api/url.html )

Просмотреть файл

@ -1,9 +1,9 @@
/*eslint-env node */
/*eslint no-process-env: 0 */
/* eslint-env node */
/* eslint no-process-env: 0 */
'use strict';
var bootlint = (process.env.BOOTLINT_COV === '1') ? require('../src-cov/bootlint.js') : require('../src/bootlint.js');
var bootlint = process.env.BOOTLINT_COV === '1' ? require('../src-cov/bootlint.js') : require('../src/bootlint.js');
var fs = require('fs');
var path = require('path');
@ -43,17 +43,13 @@ function lintHtml(html, disabledIds) {
*/
exports.bootlint = {
setUp: function (done) {
// setup here
done();
},
'HTML5 DOCTYPE': function (test) {
test.expect(4);
test.deepEqual(lintHtml(utf8Fixture('doctype/missing.html')),
["Document is missing a DOCTYPE declaration"],
['Document is missing a DOCTYPE declaration'],
'should complain when no doctype declaration is present.');
test.deepEqual(lintHtml(utf8Fixture('doctype/html4.html')),
["Document declares a non-HTML5 DOCTYPE"],
['Document declares a non-HTML5 DOCTYPE'],
'should complain when the HTML4 doctype is used.');
test.deepEqual(lintHtml(utf8Fixture('doctype/html5-normal.html')),
[],
@ -95,7 +91,7 @@ exports.bootlint = {
[],
'should not complain when X-UA-Compatible <meta> tag is present but lowercased.');
test.deepEqual(lintHtml(utf8Fixture('x-ua-compatible/missing.html')),
["`<head>` is missing X-UA-Compatible `<meta>` tag that disables old IE compatibility modes"],
['`<head>` is missing X-UA-Compatible `<meta>` tag that disables old IE compatibility modes'],
'should complain when X-UA-Compatible <meta> tag is missing.');
test.done();
},
@ -103,8 +99,8 @@ exports.bootlint = {
test.expect(1);
test.deepEqual(lintHtml(utf8Fixture('bs-v2.html')),
[
"Found one or more uses of outdated Bootstrap v2 `.spanN` grid classes",
"Only columns (`.col-*-*`) may be children of `.row`s"
'Found one or more uses of outdated Bootstrap v2 `.spanN` grid classes',
'Only columns (`.col-*-*`) may be children of `.row`s'
],
'should complain when Bootstrap v2 grid classes are present.');
test.done();
@ -121,7 +117,7 @@ exports.bootlint = {
[],
'should not complain when rows are children of columns.');
test.deepEqual(lintHtml(utf8Fixture('containers/missing.html')),
["Found one or more `.row`s that were not children of a grid column or descendants of a `.container` or `.container-fluid`"],
['Found one or more `.row`s that were not children of a grid column or descendants of a `.container` or `.container-fluid`'],
'should complain when a row is not a descendant of a container.');
test.deepEqual(lintHtml(utf8Fixture('containers/ancestor.html')),
[],
@ -131,16 +127,16 @@ exports.bootlint = {
'nested containers': function (test) {
test.expect(4);
test.deepEqual(lintHtml(utf8Fixture('containers/nested-fixed-fixed.html')),
["Containers (`.container` and `.container-fluid`) are not nestable"],
['Containers (`.container` and `.container-fluid`) are not nestable'],
'should complain when a container is within a container.');
test.deepEqual(lintHtml(utf8Fixture('containers/nested-fixed-fluid.html')),
["Containers (`.container` and `.container-fluid`) are not nestable"],
['Containers (`.container` and `.container-fluid`) are not nestable'],
'should complain when a container is within a container.');
test.deepEqual(lintHtml(utf8Fixture('containers/nested-fluid-fluid.html')),
["Containers (`.container` and `.container-fluid`) are not nestable"],
['Containers (`.container` and `.container-fluid`) are not nestable'],
'should complain when a container is within a container.');
test.deepEqual(lintHtml(utf8Fixture('containers/nested-fluid-fixed.html')),
["Containers (`.container` and `.container-fluid`) are not nestable"],
['Containers (`.container` and `.container-fluid`) are not nestable'],
'should complain when a container is within a container.');
test.done();
},
@ -150,7 +146,7 @@ exports.bootlint = {
[],
'should not complain when viewport <meta> tag is present');
test.deepEqual(lintHtml(utf8Fixture('viewport/missing.html')),
["`<head>` is missing viewport `<meta>` tag that enables responsiveness"],
['`<head>` is missing viewport `<meta>` tag that enables responsiveness'],
'should complain when viewport <meta> tag is missing.');
test.done();
},
@ -158,7 +154,7 @@ exports.bootlint = {
test.expect(1);
test.deepEqual(lintHtml(utf8Fixture('grid/row-col-same-elem.html')),
[
"Found both `.row` and `.col-*-*` used on the same element",
'Found both `.row` and `.col-*-*` used on the same element',
'Columns (`.col-*-*`) can only be children of `.row`s or `.form-group`s'
],
'should complain when .row and .col-*-* are used on the same element.');
@ -167,17 +163,17 @@ exports.bootlint = {
'row and container classes on same element': function (test) {
test.expect(2);
test.deepEqual(lintHtml(utf8Fixture('containers/fixed-row-same-elem.html')),
["Found one or more `.row`s that were not children of a grid column or descendants of a `.container` or `.container-fluid`"],
['Found one or more `.row`s that were not children of a grid column or descendants of a `.container` or `.container-fluid`'],
'should complain when .row and .container are used on the same element.');
test.deepEqual(lintHtml(utf8Fixture('containers/fluid-row-same-elem.html')),
["Found one or more `.row`s that were not children of a grid column or descendants of a `.container` or `.container-fluid`"],
['Found one or more `.row`s that were not children of a grid column or descendants of a `.container` or `.container-fluid`'],
'should complain when .row and .container-fluid are used on the same element.');
test.done();
},
'remote modals': function (test) {
test.expect(1);
test.deepEqual(lintHtml(utf8Fixture('modal/remote.html')),
["Found one or more modals using the deprecated `remote` option"],
['Found one or more modals using the deprecated `remote` option'],
'should complain when remote modals are present.');
test.done();
},
@ -190,20 +186,20 @@ exports.bootlint = {
[],
'should not complain when jQuery & a plugin is present.');
test.deepEqual(lintHtml(utf8Fixture('jquery/old-url.html')),
["Found what might be an outdated version of jQuery; Bootstrap requires jQuery v1.9.1 or higher"],
['Found what might be an outdated version of jQuery; Bootstrap requires jQuery v1.9.1 or higher'],
'should complain about old version of jQuery based on URL');
test.deepEqual(lintHtml(utf8Fixture('jquery/missing.html')),
["Unable to locate jQuery, which is required for Bootstrap's JavaScript plugins to work"],
"should complain when jQuery appears to be missing.");
['Unable to locate jQuery, which is required for Bootstrap\'s JavaScript plugins to work'],
'should complain when jQuery appears to be missing.');
test.deepEqual(lintHtml(utf8Fixture('jquery/and_bs_js_both_missing.html')),
["Unable to locate jQuery, which is required for Bootstrap's JavaScript plugins to work; however, you might not be using Bootstrap's JavaScript"],
"should complain differently when jQuery appears to be missing but Bootstrap's JS is also missing.");
['Unable to locate jQuery, which is required for Bootstrap\'s JavaScript plugins to work; however, you might not be using Bootstrap\'s JavaScript'],
'should complain differently when jQuery appears to be missing but Bootstrap\'s JS is also missing.');
test.done();
},
'bootstrap[.min].js': function (test) {
test.expect(4);
test.deepEqual(lintHtml(utf8Fixture('js/both.html')),
["Only one copy of Bootstrap's JS should be included; currently the webpage includes both bootstrap.js and bootstrap.min.js"],
['Only one copy of Bootstrap\'s JS should be included; currently the webpage includes both bootstrap.js and bootstrap.min.js'],
'should complain when both bootstrap.js and bootstrap.min.js are included.');
test.deepEqual(lintHtml(utf8Fixture('js/one.html')),
[],
@ -212,17 +208,17 @@ exports.bootlint = {
[],
'should not complain when only 1 of bootstrap.js and bootstrap.min.js is included but another JS file with "bootstrap" in its name is included.');
test.deepEqual(lintHtml(utf8Fixture('js/weird.html')),
["Only one copy of Bootstrap's JS should be included; currently the webpage includes both bootstrap.js and bootstrap.min.js"],
['Only one copy of Bootstrap\'s JS should be included; currently the webpage includes both bootstrap.js and bootstrap.min.js'],
'should complain when both bootstrap.js and bootstrap.min.js are included, even when their URLs use fragments and query strings.');
test.done();
},
'input groups with impermissible kind of form control': function (test) {
test.expect(3);
test.deepEqual(lintHtml(utf8Fixture('input-group/textarea.html')),
["`.input-group` contains a `<textarea>`; only text-based `<input>`s are permitted in an `.input-group`"],
['`.input-group` contains a `<textarea>`; only text-based `<input>`s are permitted in an `.input-group`'],
'should complain about input groups with a <textarea> form control');
test.deepEqual(lintHtml(utf8Fixture('input-group/select.html')),
["`.input-group` contains a `<select>`; this should be avoided as `<select>`s cannot be fully styled in WebKit browsers"],
['`.input-group` contains a `<select>`; this should be avoided as `<select>`s cannot be fully styled in WebKit browsers'],
'should complain about input groups with a <select> form control');
test.deepEqual(lintHtml(utf8Fixture('input-group/valid.html')),
[],
@ -233,9 +229,9 @@ exports.bootlint = {
test.expect(1);
test.deepEqual(lintHtml(utf8Fixture('tooltips/on-disabled-elems.html')),
[
"Tooltips and popovers on disabled elements cannot be triggered by user interaction unless the element becomes enabled." +
" To have tooltips and popovers be triggerable by the user even when their associated element is disabled," +
" put the disabled element inside a wrapper `<div>` and apply the tooltip or popover to the wrapper `<div>` instead.",
'Tooltips and popovers on disabled elements cannot be triggered by user interaction unless the element becomes enabled.' +
' To have tooltips and popovers be triggerable by the user even when their associated element is disabled,' +
' put the disabled element inside a wrapper `<div>` and apply the tooltip or popover to the wrapper `<div>` instead.',
'Using the `.disabled` class on a `<button>` or `<input>` only changes the appearance of the element. It doesn\'t prevent the user from interacting with the element (for example, clicking on it or focusing it). If you want to truly disable the element, use the `disabled` attribute instead.'
],
'should complain about tooltips and popovers on disabled elements.');
@ -244,52 +240,52 @@ exports.bootlint = {
'tooltips and popovers within button groups should have their container set to body': function (test) {
test.expect(1);
test.deepEqual(lintHtml(utf8Fixture('tooltips/in-btn-groups.html')),
["Tooltips and popovers within button groups should have their `container` set to `'body'`. Found tooltips/popovers that might lack this setting."],
['Tooltips and popovers within button groups should have their `container` set to `\'body\'`. Found tooltips/popovers that might lack this setting.'],
'should complain when `data-*`-based tooltips or popovers lack `data-container="body"`.');
test.done();
},
'btn/input sizing used without input-group-* size': function (test) {
test.expect(1);
test.deepEqual(lintHtml(utf8Fixture('input-group/missing-input-group-sizing.html')),
["Button and input sizing within `.input-group`s can cause issues. Instead, use input group sizing classes `.input-group-lg` or `.input-group-sm`"],
['Button and input sizing within `.input-group`s can cause issues. Instead, use input group sizing classes `.input-group-lg` or `.input-group-sm`'],
'should complain when an input/btn sizes are used within input-group.');
test.done();
},
'input groups with multiple form controls': function (test) {
test.expect(1);
test.deepEqual(lintHtml(utf8Fixture('input-group/multiple-form-controls.html')),
["Input groups cannot contain multiple `.form-control`s"],
['Input groups cannot contain multiple `.form-control`s'],
'should complain when an input group contains multiple form controls.');
test.done();
},
'mixing input groups with form groups': function (test) {
test.expect(1);
test.deepEqual(lintHtml(utf8Fixture('input-group/mixed-with-form-group.html')),
["`.input-group` and `.form-group` cannot be used directly on the same element. Instead, nest the `.input-group` within the `.form-group`"],
['`.input-group` and `.form-group` cannot be used directly on the same element. Instead, nest the `.input-group` within the `.form-group`'],
'should complain when .input-group and .form-group are used on the same element.');
test.done();
},
'mixing input groups with grid columns': function (test) {
test.expect(1);
test.deepEqual(lintHtml(utf8Fixture('input-group/mixed-with-grid-col.html')),
["`.input-group` and `.col-*-*` cannot be used directly on the same element. Instead, nest the `.input-group` within the `.col-*-*`"],
['`.input-group` and `.col-*-*` cannot be used directly on the same element. Instead, nest the `.input-group` within the `.col-*-*`'],
'should complain when an input group has a grid column class on it.');
test.done();
},
'input groups missing controls and addons': function (test) {
test.expect(2);
test.deepEqual(lintHtml(utf8Fixture('input-group/missing-input-group-addon.html')),
["`.input-group` must have a `.form-control` and either an `.input-group-addon` or an `.input-group-btn`."],
['`.input-group` must have a `.form-control` and either an `.input-group-addon` or an `.input-group-btn`.'],
'should complain when missing missing a `.form-control`');
test.deepEqual(lintHtml(utf8Fixture('input-group/missing-form-control.html')),
["`.input-group` must have a `.form-control` and either an `.input-group-addon` or an `.input-group-btn`."],
['`.input-group` must have a `.form-control` and either an `.input-group-addon` or an `.input-group-btn`.'],
'should complain when missing missing a `.input-group-addon`');
test.done();
},
'non-column children of rows': function (test) {
test.expect(2);
test.deepEqual(lintHtml(utf8Fixture('grid/non-col-row-children.html')),
["Only columns (`.col-*-*`) may be children of `.row`s"],
['Only columns (`.col-*-*`) may be children of `.row`s'],
'should complain when rows have non-column children.');
test.deepEqual(lintHtml(utf8Fixture('grid/script-child-of-row.html')),
[],
@ -299,19 +295,19 @@ exports.bootlint = {
'multiple columns on the same side of an input group': function (test) {
test.expect(5);
test.deepEqual(lintHtml(utf8Fixture('input-group/multiple-add-on-left.html')),
["Having multiple add-ons on a single side of an input group is not supported"],
['Having multiple add-ons on a single side of an input group is not supported'],
'should complain when multiple normal add-ons are on the left side of an input group.');
test.deepEqual(lintHtml(utf8Fixture('input-group/multiple-add-on-right.html')),
["Having multiple add-ons on a single side of an input group is not supported"],
['Having multiple add-ons on a single side of an input group is not supported'],
'should complain when multiple normal add-ons are on the right side of an input group.');
test.deepEqual(lintHtml(utf8Fixture('input-group/multiple-btn-add-on-left.html')),
["Having multiple add-ons on a single side of an input group is not supported"],
['Having multiple add-ons on a single side of an input group is not supported'],
'should complain when multiple button add-ons are on the left side of an input group.');
test.deepEqual(lintHtml(utf8Fixture('input-group/multiple-btn-add-on-right.html')),
["Having multiple add-ons on a single side of an input group is not supported"],
['Having multiple add-ons on a single side of an input group is not supported'],
'should complain when multiple button add-ons are on the right side of an input group.');
test.deepEqual(lintHtml(utf8Fixture('input-group/multiple-mixed-add-on-left.html')),
["Having multiple add-ons on a single side of an input group is not supported"],
['Having multiple add-ons on a single side of an input group is not supported'],
'should complain when both a normal add-on and a button add-on are on the left side of an input group.');
test.done();
},
@ -321,7 +317,7 @@ exports.bootlint = {
[],
'should not complain when correct .dropdown-toggle markup is used.');
test.deepEqual(lintHtml(utf8Fixture('buttons/btn-toggle-after-btn.html')),
["`.btn.dropdown-toggle` must be the last button in a button group."],
['`.btn.dropdown-toggle` must be the last button in a button group.'],
'should complain when `.dropdown-toggle` is on the left side of a btn');
test.done();
},
@ -338,10 +334,10 @@ exports.bootlint = {
'use disabled attribute on button.btn and input.btn instead of .disabled': function (test) {
test.expect(3);
test.deepEqual(lintHtml(utf8Fixture('buttons/button-disabled-class.html')),
["Using the `.disabled` class on a `<button>` or `<input>` only changes the appearance of the element. It doesn't prevent the user from interacting with the element (for example, clicking on it or focusing it). If you want to truly disable the element, use the `disabled` attribute instead."],
['Using the `.disabled` class on a `<button>` or `<input>` only changes the appearance of the element. It doesn\'t prevent the user from interacting with the element (for example, clicking on it or focusing it). If you want to truly disable the element, use the `disabled` attribute instead.'],
'should complain when Bootstrap v2 grid classes are present.');
test.deepEqual(lintHtml(utf8Fixture('buttons/input-btn-disabled-class.html')),
["Using the `.disabled` class on a `<button>` or `<input>` only changes the appearance of the element. It doesn't prevent the user from interacting with the element (for example, clicking on it or focusing it). If you want to truly disable the element, use the `disabled` attribute instead."],
['Using the `.disabled` class on a `<button>` or `<input>` only changes the appearance of the element. It doesn\'t prevent the user from interacting with the element (for example, clicking on it or focusing it). If you want to truly disable the element, use the `disabled` attribute instead.'],
'should complain when Bootstrap v2 grid classes are present.');
test.deepEqual(lintHtml(utf8Fixture('buttons/disabled-attribute.html')),
[],
@ -351,7 +347,7 @@ exports.bootlint = {
'inputs should set type': function (test) {
test.expect(2);
test.deepEqual(lintHtml(utf8Fixture('form-control/without-type.html')),
["Found one or more `<input>`s missing a `type` attribute."],
['Found one or more `<input>`s missing a `type` attribute.'],
'should complain about lack of type attribute on inputs');
test.deepEqual(lintHtml(utf8Fixture('form-control/with-type.html')),
[],
@ -373,10 +369,10 @@ exports.bootlint = {
'should complain when invalid .radio markup is used.');
test.deepEqual(lintHtml(utf8Fixture('checkboxes-radios/checkbox-inline-non-label.html')),
["`.checkbox-inline` should only be used on `<label>` elements"],
['`.checkbox-inline` should only be used on `<label>` elements'],
'should complain when .checkbox-inline is used on a non-<label> element.');
test.deepEqual(lintHtml(utf8Fixture('checkboxes-radios/radio-inline-non-label.html')),
["`.radio-inline` should only be used on `<label>` elements"],
['`.radio-inline` should only be used on `<label>` elements'],
'should complain when .radio-inline is used on a non-<label> element.');
test.deepEqual(lintHtml(utf8Fixture('checkboxes-radios/checkbox-inline-bad-structure.html')),
@ -395,20 +391,20 @@ exports.bootlint = {
[],
'should not complain when .active and checked correspond correctly.');
test.deepEqual(lintHtml(utf8Fixture('buttons-plugin/checkbox-bad.html')),
["`.active` class used without the `checked` attribute (or vice-versa) in a button group using the button.js plugin"],
['`.active` class used without the `checked` attribute (or vice-versa) in a button group using the button.js plugin'],
'should complain when .active and checked do not correspond correctly in a checkbox button group.');
test.deepEqual(lintHtml(utf8Fixture('buttons-plugin/radio-bad.html')),
["`.active` class used without the `checked` attribute (or vice-versa) in a button group using the button.js plugin"],
['`.active` class used without the `checked` attribute (or vice-versa) in a button group using the button.js plugin'],
'should complain when .active and checked do not correspond correctly in a radio button group.');
test.done();
},
'modals within other components': function (test) {
test.expect(2);
test.deepEqual(lintHtml(utf8Fixture('modal/within-table.html')),
["Modal markup should not be placed within other components, so as to avoid the component's styles interfering with the modal's appearance or functionality"],
['Modal markup should not be placed within other components, so as to avoid the component\'s styles interfering with the modal\'s appearance or functionality'],
'should complain when a modal is placed within a `.table`.');
test.deepEqual(lintHtml(utf8Fixture('modal/within-navbar.html')),
["Modal markup should not be placed within other components, so as to avoid the component's styles interfering with the modal's appearance or functionality"],
['Modal markup should not be placed within other components, so as to avoid the component\'s styles interfering with the modal\'s appearance or functionality'],
'should complain when a modal is placed within a `.navbar`.');
test.done();
},
@ -419,16 +415,16 @@ exports.bootlint = {
[],
'should not complain when panel is structured correctly.');
test.deepEqual(lintHtml(utf8Fixture('panels/panel-body-missing-parent.html')),
["`.panel-body` must have a `.panel` or `.panel-collapse` parent"],
['`.panel-body` must have a `.panel` or `.panel-collapse` parent'],
'should complain when .panel-body is missing .panel or .panel-collapse parent');
test.deepEqual(lintHtml(utf8Fixture('panels/panel-footer-missing-parent.html')),
["`.panel-footer` must have a `.panel` or `.panel-collapse` parent"],
['`.panel-footer` must have a `.panel` or `.panel-collapse` parent'],
'should complain when .panel-footer is missing .panel parent');
test.deepEqual(lintHtml(utf8Fixture('panels/panel-title-missing-parent.html')),
["`.panel-title` must have a `.panel-heading` parent"],
['`.panel-title` must have a `.panel-heading` parent'],
'should complain when .panel-title is missing .panel-heading parent');
test.deepEqual(lintHtml(utf8Fixture('panels/panel-heading-missing-parent.html')),
["`.panel-heading` must have a `.panel` parent"],
['`.panel-heading` must have a `.panel` parent'],
'should complain when .panel-heading is missing .panel parent');
test.done();
},
@ -444,7 +440,7 @@ exports.bootlint = {
'should not complain when columns are within a form group.'
);
test.deepEqual(lintHtml(utf8Fixture('grid/cols-outside-row-and-form-group.html')),
["Columns (`.col-*-*`) can only be children of `.row`s or `.form-group`s"],
['Columns (`.col-*-*`) can only be children of `.row`s or `.form-group`s'],
'should complain when columns are outside of rows and form groups.'
);
test.done();
@ -457,7 +453,7 @@ exports.bootlint = {
'should not complain when .table-responsive is used on the table\'s wrapper div.'
);
test.deepEqual(lintHtml(utf8Fixture('table/responsive-incorrect.html')),
["`.table-responsive` is supposed to be used on the table's parent wrapper `<div>`, not on the table itself"],
['`.table-responsive` is supposed to be used on the table\'s parent wrapper `<div>`, not on the table itself'],
'should complain when .table-responsive is used on the table itself.'
);
test.done();
@ -535,11 +531,11 @@ exports.bootlint = {
'should not complain when .form-control-feedback has a correct ancestor.'
);
test.deepEqual(lintHtml(utf8Fixture('feedback/form-control-bad.html')),
["`.form-control-feedback` must have a `.form-group.has-feedback` ancestor"],
['`.form-control-feedback` must have a `.form-group.has-feedback` ancestor'],
'should complain when .form-control-feedback doesn\'t have a .form-group.has-feedback ancestor.'
);
test.deepEqual(lintHtml(utf8Fixture('feedback/nested-form-control-bad.html')),
["`.form-control-feedback` must have a `.form-group.has-feedback` ancestor"],
['`.form-control-feedback` must have a `.form-group.has-feedback` ancestor'],
'should complain when a nested .form-control-feedback doesn\'t have a .form-group.has-feedback ancestor.'
);
test.done();
@ -552,11 +548,11 @@ exports.bootlint = {
'should not complain when the .glyphicon element has no text content or children.'
);
test.deepEqual(lintHtml(utf8Fixture('glyphicons/on-elem-with-text.html')),
["Glyphicon classes must only be used on elements that contain no text content and have no child elements."],
['Glyphicon classes must only be used on elements that contain no text content and have no child elements.'],
'should complain when Glyphicon is used on an element with text content.'
);
test.deepEqual(lintHtml(utf8Fixture('glyphicons/on-elem-with-child.html')),
["Glyphicon classes must only be used on elements that contain no text content and have no child elements."],
['Glyphicon classes must only be used on elements that contain no text content and have no child elements.'],
'should complain when Glyphicon is used on an element with child element(s).'
);
test.done();
@ -569,7 +565,7 @@ exports.bootlint = {
'should not complain when Glyphicon is used correctly.'
);
test.deepEqual(lintHtml(utf8Fixture('glyphicons/missing-glyphicon-class.html')),
["Found elements with a `.glyphicon-*` class that were missing the additional required `.glyphicon` class."],
['Found elements with a `.glyphicon-*` class that were missing the additional required `.glyphicon` class.'],
'should complain when .glyphicon-* class is used without the .glyphicon class.'
);
test.done();
@ -582,27 +578,27 @@ exports.bootlint = {
'should not complain when modal markup structure is correct.'
);
test.deepEqual(lintHtml(utf8Fixture('modal/dialog-outside-modal.html')),
["`.modal-dialog` must be a child of `.modal`"],
['`.modal-dialog` must be a child of `.modal`'],
'should complain when modal dialog not within modal.'
);
test.deepEqual(lintHtml(utf8Fixture('modal/content-outside-dialog.html')),
["`.modal-content` must be a child of `.modal-dialog`"],
['`.modal-content` must be a child of `.modal-dialog`'],
'should complain when modal content not within modal dialog.'
);
test.deepEqual(lintHtml(utf8Fixture('modal/header-outside-content.html')),
["`.modal-header` must be a child of `.modal-content`"],
['`.modal-header` must be a child of `.modal-content`'],
'should complain when modal header not within modal content'
);
test.deepEqual(lintHtml(utf8Fixture('modal/body-outside-content.html')),
["`.modal-body` must be a child of `.modal-content`"],
['`.modal-body` must be a child of `.modal-content`'],
'should complain when modal body not within modal content.'
);
test.deepEqual(lintHtml(utf8Fixture('modal/footer-outside-content.html')),
["`.modal-footer` must be a child of `.modal-content`"],
['`.modal-footer` must be a child of `.modal-content`'],
'should complain when modal footer not within modal content.'
);
test.deepEqual(lintHtml(utf8Fixture('modal/title-outside-header.html')),
["`.modal-title` must be a child of `.modal-header`"],
['`.modal-title` must be a child of `.modal-header`'],
'should complain when modal title is not within modal header.'
);
test.done();
@ -611,11 +607,11 @@ exports.bootlint = {
'form classes used directly on form groups': function (test) {
test.expect(2);
test.deepEqual(lintHtml(utf8Fixture('form/form-inline-group.html')),
["Neither `.form-inline` nor `.form-horizontal` should be used directly on a `.form-group`. Instead, nest the `.form-group` within the `.form-inline` or `.form-horizontal`"],
['Neither `.form-inline` nor `.form-horizontal` should be used directly on a `.form-group`. Instead, nest the `.form-group` within the `.form-inline` or `.form-horizontal`'],
'should complain about form-group having .form-inline'
);
test.deepEqual(lintHtml(utf8Fixture('form/form-horizontal-group.html')),
["Neither `.form-inline` nor `.form-horizontal` should be used directly on a `.form-group`. Instead, nest the `.form-group` within the `.form-inline` or `.form-horizontal`"],
['Neither `.form-inline` nor `.form-horizontal` should be used directly on a `.form-group`. Instead, nest the `.form-group` within the `.form-inline` or `.form-horizontal`'],
'should complain about form-group having .form-horizontal'
);
test.done();
@ -836,7 +832,7 @@ exports.bootlint = {
'container inside navbar': function (test) {
test.expect(1);
test.deepEqual(lintHtml(utf8Fixture('navbar/navbar-container.html')),
["`.navbar`'s first child element should always be either `.container` or `.container-fluid`"],
['`.navbar`\'s first child element should always be either `.container` or `.container-fluid`'],
'should complain about no .container/.container-fluid inside .navbar.'
);
test.done();

Просмотреть файл

@ -1,11 +1,11 @@
/*eslint-env node */
/*eslint no-process-env: 0 */
/* eslint-env node */
/* eslint no-process-env: 0 */
'use strict';
var sinon = require('sinon');
var rewire = require('rewire');
var cli = (process.env.BOOTLINT_COV === '1') ? rewire('../src-cov/cli.js') : rewire('../src/cli.js');
var cli = process.env.BOOTLINT_COV === '1' ? rewire('../src-cov/cli.js') : rewire('../src/cli.js');
/*
======== A Handy Little Nodeunit Reference ========
@ -38,14 +38,14 @@ function rAfter() {
}
exports.bootlint = {
setUp: function (done) {
'setUp': function (done) {
oldTTY = process.stdin.isTTY;
sinon.stub(process.stdout, 'write');
sinon.stub(process.stderr, 'write');
done();
},
tearDown: function (done) {
'tearDown': function (done) {
process.stdin.isTTY = oldTTY;
process.chdir(startingDir);
@ -66,6 +66,7 @@ exports.bootlint = {
test.strictEqual(message, '0 lint error(s) found across 0 file(s).');
break;
}
// no default
}
i++;

5
test/fixtures/generic-qunit.js поставляемый
Просмотреть файл

@ -1,6 +1,5 @@
/* global QUnit, expect, bootlint */
/* jshint browser: true, jquery: true */
/*eslint-env browser */
/* eslint-env browser, jquery, qunit */
/* global bootlint */
(function () {
'use strict';

Просмотреть файл

@ -1,9 +1,9 @@
/*eslint-env node */
/*eslint no-process-env: 0 */
/* eslint-env node */
/* eslint no-process-env: 0 */
'use strict';
var _location = (process.env.BOOTLINT_COV === '1') ? require('../src-cov/location.js') : require('../src/location.js');
var _location = process.env.BOOTLINT_COV === '1' ? require('../src-cov/location.js') : require('../src/location.js');
var Location = _location.Location;
var LocationIndex = _location.LocationIndex;
@ -35,10 +35,6 @@ var EXAMPLE = [
var EXAMPLE_WITHOUT_TERMINATOR = EXAMPLE.substring(0, EXAMPLE.length - 1);
exports.bootlint = {
setUp: function (done) {
// setup here
done();
},
'LocationIndex with string terminated by a newline': function (test) {
var index = new LocationIndex(EXAMPLE);
test.expect(10);