diff --git a/README.md b/README.md index aaf64d17..f347147f 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,16 @@ $ bootlint << EOF EOF ``` +Customized Bootstrap grid support with options: +`--cols` (or `-c`) to set number of columns. +`--screens` (or `-s`) which takes a comma-separated list of screen sizes. +Here's an example: + +```shell +$ bootlint -c 20 -s xxs,xs,sm,md,lg,xlg /path/to/some/webpage.html another_webpage.html [...] +``` + + ### In the browser Use the following [bookmarklet](https://en.wikipedia.org/wiki/Bookmarklet) that's powered by [BootstrapCDN](https://www.bootstrapcdn.com/bootlint/): @@ -118,12 +128,18 @@ A ***reporter*** is a function that accepts exactly 1 argument of type `LintWarn Bootlint exports a `bootlint` property on the global `window` object. In a browser environment, the following public APIs are available: -* `bootlint.lintCurrentDocument(reporter, disabledIds)`: Lints the HTML of the current document and calls the `reporter()` function repeatedly with each lint problem as an argument. +* `bootlint.lintCurrentDocument(reporter, options)`: Lints the HTML of the current document and calls the `reporter()` function repeatedly with each lint problem as an argument. * `reporter` is a *reporter* function (see above for a definition). It will be called repeatedly with each lint problem as an argument. - * `disabledIds` is an array of string linter IDs to disable + * `options` is an optional object of configuration options + * `cols` is number of bootstrap columns + * `disabledIds` is an array of string linter IDs to disable + * `screens` is an array of custom screen sizes (e.g. `['xxs', 'xs', 'sm', 'md', 'lg', 'xlg']`) * Returns nothing (i.e. `undefined`) * `bootlint.showLintReportForCurrentDocument(disabledIds, alertOpts)`: Lints the HTML of the current document and reports the linting results to the user. Each warning will be output individually using `console.warn()`. - * `disabledIds` is an array of string linter IDs to disable + * `options` is an optional object of configuration options + * `cols` is number of bootstrap columns + * `disabledIds` is an array of string linter IDs to disable + * `screens` is an array of custom screen sizes (e.g. `['xxs', 'xs', 'sm', 'md', 'lg', 'xlg']`) * `alertOpts` is an optional options object with the following properties: * `hasProblems` (type: `boolean`; default: `true`) - `window.alert()` a single general notification message to the user if there are any lint problems? * `problemFree` (type: `boolean`; default: `true`) - `window.alert()` a notification message to the user if the document has no lint problems? @@ -140,15 +156,18 @@ function reporter(lint) { console.log(lint.id, lint.message); } -bootlint.lintHtml("...", reporter, []); // calls reporter() repeatedly with each lint problem as an argument +bootlint.lintHtml("...", reporter); // calls reporter() repeatedly with each lint problem as an argument ``` In a Node.js environment, Bootlint exposes the following public API: -* `bootlint.lintHtml(html, reporter, disabledIds)`: Lints the given HTML for a webpage and returns the linting results. +* `bootlint.lintHtml(html, reporter, options)`: Lints the given HTML for a webpage and returns the linting results. * `html` is the HTML to lint, as a string * `reporter` is a *reporter* function (see above for a definition). It will be called repeatedly with each lint problem as an argument. - * `disabledIds` is an array of string linter IDs to disable + * `options` is an optional object of configuration options + * `cols` is number of bootstrap columns + * `disabledIds` is an array of string linter IDs to disable + * `screens` is an array of custom screen sizes (e.g. `['xxs', 'xs', 'sm', 'md', 'lg', 'xlg']`) * Returns nothing (i.e. `undefined`) ### HTTP API diff --git a/src/bootlint.js b/src/bootlint.js index e9650c08..6ae5def7 100644 --- a/src/bootlint.js +++ b/src/bootlint.js @@ -62,6 +62,43 @@ var LocationIndex = _location.LocationIndex; ].join(','); var WIKI_URL = 'https://github.com/twbs/bootlint/wiki/'; + exports.configure = function (options) { + var changed = false; + + if (options.cols) { + NUM_COLS = options.cols || NUM_COLS; + changed = true; + } + + if (options.screens) { + if (typeof options.screens === 'string') { + options.screens = options.screens.split(','); + } + + SCREENS = options.screens; + changed = true; + } + + if (changed) { + SCREEN2NUM = {}; + NUM2SCREEN = []; + COL_CLASSES = []; + + SCREENS.forEach(function (screen, index) { + SCREEN2NUM[screen] = index; + NUM2SCREEN[index] = screen; + + for (var n = 1; n <= NUM_COLS; n++) { + COL_CLASSES.push('.col-' + screen + '-' + n); + } + }); + + var colPattern = '\\bcol-(' + SCREENS.join('|') + ')-(\\d{1,2})\\b'; + COL_REGEX = new RegExp(colPattern); + COL_REGEX_G = new RegExp(colPattern, 'g'); + } + }; + function compareNums(a, b) { return a - b; } @@ -1099,7 +1136,8 @@ var LocationIndex = _location.LocationIndex; reporter('`.modal-dialog` must have a `role="document"` attribute.', modalDialogs); } }); - exports._lint = function ($, reporter, disabledIdList, html) { + exports._lint = function ($, reporter, options, html) { + options = options || {}; var locationIndex = IN_NODE_JS ? new LocationIndex(html) : null; var reporterWrapper = IN_NODE_JS ? function (problem) { if (problem.elements) { @@ -1115,10 +1153,21 @@ var LocationIndex = _location.LocationIndex; reporter(problem); } : reporter; + // Backward compatibility with previous param disabledIdList + if (options instanceof Array) { + options = { + disabledIds: options + }; + } + + this.configure(options); + var disabledIdSet = {}; - disabledIdList.forEach(function (disabledId) { - disabledIdSet[disabledId] = true; - }); + if (options.disabledIds instanceof Array) { + options.disabledIds.forEach(function (disabledId) { + disabledIdSet[disabledId] = true; + }); + } Object.keys(allLinters).sort().forEach(function (linterId) { if (!disabledIdSet[linterId]) { allLinters[linterId]($, reporterWrapper); @@ -1137,12 +1186,15 @@ var LocationIndex = _location.LocationIndex; * Lints the given HTML. * @param {string} html The HTML to lint * @param {reporter} reporter Function to call with each lint problem - * @param {string[]} disabledIds Array of string IDs of linters to disable + * @param {object} [options] Options object to configure linting + * @param {integer} [options.cols] Number of bootstrap columns + * @param {string[]} [options.disabledIds=[]] Array of string IDs of linters to disable + * @param {string[]} [options.screens=[]] Array of custom screen sizes * @returns {undefined} Nothing */ - exports.lintHtml = function (html, reporter, disabledIds) { + exports.lintHtml = function (html, reporter, options) { var $ = cheerio.load(html, {withStartIndices: true}); - this._lint($, reporter, disabledIds, html); + this._lint($, reporter, options, html); }; } else { // jQuery; in-browser @@ -1152,23 +1204,30 @@ var LocationIndex = _location.LocationIndex; /** * Lints the HTML of the current document. * @param {reporter} reporter Function to call with each lint problem - * @param {string[]} disabledIds Array of string IDs of linters to disable + * @param {object} [options] Options object to configure linting + * @param {integer} [options.cols] Number of bootstrap columns + * @param {string[]} [options.disabledIds=[]] Array of string IDs of linters to disable + * @param {string[]} [options.screens=[]] Array of custom screen sizes * @returns {undefined} Nothing */ - exports.lintCurrentDocument = function (reporter, disabledIds) { - this._lint($, reporter, disabledIds); + exports.lintCurrentDocument = function (reporter, options) { + this._lint($, reporter, options); }; /** * Lints the HTML of the current document. * If there are any lint warnings, one general notification message will be window.alert()-ed to the user. * Each warning will be output individually using console.warn(). - * @param {string[]} disabledIds Array of string IDs of linters to disable + * @param {object} [options] Options object to configure linting + * @param {integer} [options.cols] Number of bootstrap columns + * @param {string[]} [options.disabledIds=[]] Array of string IDs of linters to disable + * @param {string[]} [options.screens=[]] Array of custom screen sizes * @param {object} [alertOpts] Options object to configure alert()ing * @param {boolean} [alertOpts.hasProblems=true] Show one alert() when the first lint problem is found? * @param {boolean} [alertOpts.problemFree=true] Show one alert() at the end of linting if the page has no lint problems? * @returns {undefined} Nothing */ - exports.showLintReportForCurrentDocument = function (disabledIds, alertOpts) { + exports.showLintReportForCurrentDocument = function (options, alertOpts) { + options = options || {}; alertOpts = alertOpts || {}; var alertOnFirstProblem = alertOpts.hasProblems || alertOpts.hasProblems === undefined; var alertIfNoProblems = alertOpts.problemFree || alertOpts.problemFree === undefined; @@ -1193,7 +1252,7 @@ var LocationIndex = _location.LocationIndex; } errorCount++; }; - this.lintCurrentDocument(reporter, disabledIds); + this.lintCurrentDocument(reporter, options); if (errorCount > 0) { console.info('bootlint: For details, look up the lint problem IDs in the Bootlint wiki: https://github.com/twbs/bootlint/wiki'); diff --git a/src/cli.js b/src/cli.js index b2b465c5..3affec00 100644 --- a/src/cli.js +++ b/src/cli.js @@ -17,9 +17,19 @@ module.exports = function () { .option('-d, --disable ', 'Comma-separated list of disabled lint problem IDs', function (val) { return val.split(','); }) + .option('-c, --cols ', 'Number of grid columns', function (val) { + return parseInt(val, 10); + }) + .option('-s, --screens ', 'Comma-separated list of custom screen sizes', function (val) { + return val.split(','); + }) .parse(process.argv); - var disabledIds = program.disable === undefined ? [] : program.disable; + var options = { + cols: program.cols, + disabledIds: program.disable === undefined ? [] : program.disable, + screens: program.screens === undefined ? [] : program.screens + }; var totalErrCount = 0; var totalFileCount = 0; var lintedFiles = []; @@ -58,7 +68,7 @@ module.exports = function () { }); process.stdin.on('end', function () { - bootlint.lintHtml(stdInput.join(''), buildReporter(''), disabledIds); + bootlint.lintHtml(stdInput.join(''), buildReporter(''), options); totalFileCount++; resolve(); }); @@ -74,7 +84,7 @@ module.exports = function () { }); }) .each(function (file) { - bootlint.lintHtml(file.contents, buildReporter(file.name), disabledIds); + bootlint.lintHtml(file.contents, buildReporter(file.name), options); totalFileCount++; return Deferred.resolve(); }); diff --git a/test/bootlint_test.js b/test/bootlint_test.js index dc47ecab..faa86579 100644 --- a/test/bootlint_test.js +++ b/test/bootlint_test.js @@ -13,12 +13,12 @@ function utf8Fixture(name) { function utf16Fixture(name) { return fs.readFileSync(_fixtureNameToFilepath(name), {encoding: 'utf16le'}); } -function lintHtml(html, disabledIds) { +function lintHtml(html, options) { var lints = []; var reporter = function (lint) { lints.push(lint.message); }; - bootlint.lintHtml(html, reporter, disabledIds || []); + bootlint.lintHtml(html, reporter, options || {}); return lints; } /* @@ -427,12 +427,20 @@ exports.bootlint = { }, 'columns outside of rows and form groups': function (test) { - test.expect(3); + test.expect(5); test.deepEqual(lintHtml(utf8Fixture('grid/cols-within-row.html')), [], 'should not complain when columns are within a row.' ); - test.deepEqual(lintHtml(utf8Fixture('grid/cols-within-form-group.html')), + test.deepEqual(lintHtml(utf8Fixture('grid/col-lg-20.html'), {cols: 20}), + [], + 'should not complain when there is own number of grid columns and proper options.' + ); + test.deepEqual(lintHtml(utf8Fixture('grid/col-xlg-10.html'), {screens: ['xxs', 'xlg']}), + [], + 'should not complain when columns have custom screen sizes and proper options.' + ); + test.deepEqual(lintHtml(utf8Fixture('grid/cols-within-form-group.html'), {cols: 12, screens: ['xs', 'sm', 'md', 'lg']}), [], 'should not complain when columns are within a form group.' ); diff --git a/test/fixtures/generic-qunit.js b/test/fixtures/generic-qunit.js index 6048a8dd..958b3093 100644 --- a/test/fixtures/generic-qunit.js +++ b/test/fixtures/generic-qunit.js @@ -4,12 +4,12 @@ (function () { 'use strict'; - function lintCurrentDoc() { + function lintCurrentDoc(options) { var lints = []; var reporter = function (lint) { lints.push(lint.message); }; - bootlint.lintCurrentDocument(reporter, []); + bootlint.lintCurrentDocument(reporter, options); return lints; } @@ -23,7 +23,8 @@ var expectedLintMsgs = lints.map(function (item) { return item.dataset.lint; }); - var actualLintMsgs = lintCurrentDoc(); + var bootlintOptions = $('#bootlint').data('bootlint-options'); + var actualLintMsgs = lintCurrentDoc(bootlintOptions); assert.deepEqual(actualLintMsgs, expectedLintMsgs); }); })(); diff --git a/test/fixtures/grid/col-lg-20.html b/test/fixtures/grid/col-lg-20.html new file mode 100644 index 00000000..8ff5d999 --- /dev/null +++ b/test/fixtures/grid/col-lg-20.html @@ -0,0 +1,29 @@ + + + + + + + Test + + + + + + + + + +
+
+
Content
+
+
+ +
+
    + + diff --git a/test/fixtures/grid/col-xlg-10.html b/test/fixtures/grid/col-xlg-10.html new file mode 100644 index 00000000..169cebf3 --- /dev/null +++ b/test/fixtures/grid/col-xlg-10.html @@ -0,0 +1,29 @@ + + + + + + + Test + + + + + + + + + +
    +
    +
    Content
    +
    +
    + +
    +
      + +