stylelint
Advanced tools
Comparing version 1.2.1 to 2.0.0
@@ -0,1 +1,16 @@ | ||
# 2.0.0 | ||
* Added: CLI. | ||
* Added: standalone Node API. | ||
* Added: quiet mode to CLI and Node API. | ||
* Added: support for formatters, including custom ones, to CLI and Node API. | ||
* Added: `string` and `json` formatters. | ||
* Added: support for using `.stylelintrc` JSON file. | ||
* Added: support for extending existing configs using the `extends` property. | ||
* Added: support for SCSS syntax parsing to CLI and Node API. | ||
* Added: `function-comma-newline-after` rule. | ||
* Added: `function-comma-newline-before` rule. | ||
* Added: `"always-single-line"` and `"never-single-line"` options to `function-comma-space-after` rule. | ||
* Added: `"always-single-line"` and `"never-single-line"` options to `function-comma-space-before` rule. | ||
# 1.2.1 | ||
@@ -2,0 +17,0 @@ |
@@ -27,3 +27,3 @@ #!/usr/bin/env node | ||
f: "string", | ||
q: "false" | ||
q: false | ||
}, | ||
@@ -35,5 +35,6 @@ alias: { | ||
}; | ||
var syntaxOptions = ["scss"]; | ||
var meowOptions = { | ||
help: ["Usage", " stylelint [input] [options]", "", "By default, stylelint will look for a .stylelintrc file in JSON format,", "using rc to look in various places (cf. https://github.com/dominictarr/rc#standards).", "Alternately, you can specify a configuration file via --config.", "", "Input", " File glob(s) (passed to node-glob).", " You can also pass no input and use stdin.", "", "Options", " --config Path to a JSON configuration file.", " --version Get the currently installed version of stylelint.", " --custom-formatter Path to a JS file exporting a custom formatting function", " -f, --formatter Specify a formatter: \"json\" or \"string\". Default is \"string\".", " -q, --quiet Only register warnings for rules with a severity of 2 (ignore level 1)"], | ||
help: ["Usage", " stylelint [input] [options]", "", "By default, stylelint will look for a .stylelintrc file in JSON format,", "using rc to look in various places (cf. https://github.com/dominictarr/rc#standards).", "Alternately, you can specify a configuration file via --config.", "", "Input", " File glob(s) (passed to node-glob).", " You can also pass no input and use stdin.", "", "Options", " --config Path to a JSON configuration file.", " --version Get the currently installed version of stylelint.", " --custom-formatter Path to a JS file exporting a custom formatting function", " -f, --formatter Specify a formatter: \"json\" or \"string\". Default is \"string\".", " -q, --quiet Only register warnings for rules with a severity of 2 (ignore level 1)", " -s, --syntax Specify a non-standard syntax that should be used to ", " parse source stylesheets. Options: \"scss\""], | ||
pkg: "../package.json" | ||
@@ -46,19 +47,25 @@ }; | ||
var configBase = { | ||
var optionsBase = { | ||
formatter: formatter, | ||
config: { | ||
quiet: cli.flags.quiet | ||
} | ||
configOverrides: {} | ||
}; | ||
var configReady = cli.input.length ? Promise.resolve((0, _lodash.assign)({}, configBase, { | ||
if (cli.flags.quiet) { | ||
optionsBase.configOverrides.quiet = cli.flags.quiet; | ||
} | ||
if (cli.flags.s && (0, _lodash.includes)(syntaxOptions, cli.flags.s)) { | ||
optionsBase.syntax = cli.flags.s; | ||
} | ||
var optionsReady = cli.input.length ? Promise.resolve((0, _lodash.assign)({}, optionsBase, { | ||
files: cli.input | ||
})) : (0, _getStdin2["default"])().then(function (stdin) { | ||
return Promise.resolve((0, _lodash.assign)({}, configBase, { | ||
css: stdin | ||
return Promise.resolve((0, _lodash.assign)({}, optionsBase, { | ||
code: stdin | ||
})); | ||
}); | ||
configReady.then(function (config) { | ||
(0, _standalone2["default"])(config).then(function (_ref) { | ||
optionsReady.then(function (options) { | ||
(0, _standalone2["default"])(options).then(function (_ref) { | ||
var output = _ref.output; | ||
@@ -65,0 +72,0 @@ var errored = _ref.errored; |
@@ -16,7 +16,9 @@ "use strict"; | ||
exports["default"] = function (root, result) { | ||
result.disabledRanges = []; | ||
result.stylelint = result.stylelint || {}; | ||
var disabledRanges = result.stylelint.disabledRanges = []; | ||
var withinDisabledRange = false; | ||
result.root.walkComments(function (comment) { | ||
root.walkComments(function (comment) { | ||
var text = comment.text; | ||
@@ -61,3 +63,3 @@ | ||
result.disabledRanges.push(rangeObj); | ||
disabledRanges.push(rangeObj); | ||
} | ||
@@ -67,3 +69,3 @@ | ||
// Add an `end` prop to the last range | ||
result.disabledRanges[result.disabledRanges.length - 1].end = node.source.end.line; | ||
disabledRanges[disabledRanges.length - 1].end = node.source.end.line; | ||
} | ||
@@ -70,0 +72,0 @@ }; |
@@ -10,2 +10,4 @@ "use strict"; | ||
validateOptions: require("./utils/validateOptions") | ||
}; | ||
}; | ||
module.exports.lint = require("./standalone"); |
@@ -13,2 +13,18 @@ "use strict"; | ||
var _rc = require("rc"); | ||
var _rc2 = _interopRequireDefault(_rc); | ||
var _path = require("path"); | ||
var _path2 = _interopRequireDefault(_path); | ||
var _resolveFrom = require("resolve-from"); | ||
var _resolveFrom2 = _interopRequireDefault(_resolveFrom); | ||
var _lodash = require("lodash"); | ||
var _utils = require("./utils"); | ||
var _rules = require("./rules"); | ||
@@ -22,25 +38,49 @@ | ||
exports["default"] = _postcss2["default"].plugin("stylelint", function (settings) { | ||
exports["default"] = _postcss2["default"].plugin("stylelint", function () { | ||
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; | ||
return function (root, result) { | ||
if (!settings) { | ||
return; | ||
// result.stylelint is the namespace for passing stylelint-related | ||
// configuration and data across sub-plugins via the PostCSS Result | ||
result.stylelint = result.stylelint || {}; | ||
result.stylelint.ruleSeverities = {}; | ||
var initialConfig = options.hasOwnProperty("config") ? options.config : options; | ||
if ((0, _lodash.isEmpty)(initialConfig)) { | ||
initialConfig = (0, _rc2["default"])("stylelint"); | ||
} | ||
if (!settings.rules) { | ||
return; | ||
var configBasedir = options.configBasedir || _path2["default"].dirname(initialConfig.config); | ||
var config = extendConfig(initialConfig, configBasedir); | ||
if (config.plugins) { | ||
Object.keys(config.plugins).forEach(function (pluginName) { | ||
_rules2["default"][pluginName] = require(modulePath(config.plugins[pluginName], configBasedir)); | ||
}); | ||
} | ||
if (settings.plugins) { | ||
addPluginsToDefinitions(settings.plugins, _rules2["default"]); | ||
if (options.configOverrides) { | ||
(0, _lodash.merge)(config, options.configOverrides); | ||
} | ||
// First check for disabled ranges | ||
if (!config) { | ||
throw (0, _utils.configurationError)("No configuration provided"); | ||
} | ||
if (!config.rules) { | ||
throw (0, _utils.configurationError)("No rules found within configuration"); | ||
} | ||
// Register details about the configuration | ||
result.stylelint.quiet = config.quiet; | ||
// First check for disabled ranges, adding them to the result object | ||
(0, _disableRanges2["default"])(root, result); | ||
Object.keys(settings.rules).forEach(function (ruleName) { | ||
Object.keys(config.rules).forEach(function (ruleName) { | ||
if (!_rules2["default"][ruleName]) { | ||
throw new Error("Undefined rule " + ruleName); | ||
throw (0, _utils.configurationError)("Undefined rule " + ruleName); | ||
} | ||
// If severity is 0, run nothing | ||
var ruleSettings = settings.rules[ruleName]; | ||
var ruleSettings = config.rules[ruleName]; | ||
var ruleSeverity = Array.isArray(ruleSettings) ? ruleSettings[0] : ruleSettings; | ||
@@ -51,3 +91,6 @@ if (ruleSeverity === 0) { | ||
// Otherwise, run the rule with the primary and secondary options | ||
// Log the rule's severity | ||
result.stylelint.ruleSeverities[ruleName] = ruleSeverity; | ||
// Run the rule with the primary and secondary options | ||
_rules2["default"][ruleName](ruleSettings[1], ruleSettings[2])(root, result); | ||
@@ -58,11 +101,24 @@ }); | ||
function addPluginsToDefinitions(plugins, definitions) { | ||
Object.keys(plugins).forEach(function (name) { | ||
if (typeof plugins[name] !== "function") { | ||
addPluginsToDefinitions(plugins[name], definitions); | ||
return; | ||
} | ||
definitions[name] = plugins[name]; | ||
}); | ||
function extendConfig(config, configBasedir) { | ||
if (!config["extends"]) { | ||
return config; | ||
} | ||
return [].concat(config["extends"]).reduce(function (mergedConfig, extendingConfigLookup) { | ||
var extendingConfigPath = modulePath(extendingConfigLookup, configBasedir || process.cwd()); | ||
// Now we must recursively extend the extending config | ||
var extendingConfig = extendConfig(require(extendingConfigPath), _path2["default"].dirname(extendingConfigPath)); | ||
return (0, _lodash.merge)({}, extendingConfig, mergedConfig); | ||
}, (0, _lodash.cloneDeep)(config)); | ||
} | ||
function modulePath(lookup, basedir) { | ||
try { | ||
return (0, _resolveFrom2["default"])(basedir, lookup); | ||
} catch (e) { | ||
throw (0, _utils.configurationError)("Could not find \"" + lookup + "\". " + "Do you need a `configBasedir`?"); | ||
} | ||
} | ||
module.exports = exports["default"]; |
@@ -19,2 +19,8 @@ "use strict"; | ||
return "Unexpected whitespace after \",\""; | ||
}, | ||
expectedAfterSingleLine: function expectedAfterSingleLine() { | ||
return "Expected single space after \",\" in a single-line list"; | ||
}, | ||
rejectedAfterSingleLine: function rejectedAfterSingleLine() { | ||
return "Unexpected whitespace after \",\" in a single-line list"; | ||
} | ||
@@ -30,3 +36,3 @@ }); | ||
actual: expectation, | ||
possible: ["always", "never"] | ||
possible: ["always", "never", "always-single-line", "never-single-line"] | ||
}); | ||
@@ -33,0 +39,0 @@ if (!validOptions) { |
@@ -20,2 +20,8 @@ "use strict"; | ||
return "Unexpected whitespace before \",\""; | ||
}, | ||
expectedBeforeSingleLine: function expectedBeforeSingleLine() { | ||
return "Expected single space before \",\" in a single-line list"; | ||
}, | ||
rejectedBeforeSingleLine: function rejectedBeforeSingleLine() { | ||
return "Unexpected whitespace before \",\" in a single-line list"; | ||
} | ||
@@ -31,3 +37,3 @@ }); | ||
actual: expectation, | ||
possible: ["always", "never"] | ||
possible: ["always", "never", "always-single-line", "never-single-line"] | ||
}); | ||
@@ -34,0 +40,0 @@ if (!validOptions) { |
@@ -129,2 +129,10 @@ "use strict"; | ||
var _functionCommaNewlineAfter = require("./function-comma-newline-after"); | ||
var _functionCommaNewlineAfter2 = _interopRequireDefault(_functionCommaNewlineAfter); | ||
var _functionCommaNewlineBefore = require("./function-comma-newline-before"); | ||
var _functionCommaNewlineBefore2 = _interopRequireDefault(_functionCommaNewlineBefore); | ||
var _functionCommaSpaceAfter = require("./function-comma-space-after"); | ||
@@ -389,2 +397,4 @@ | ||
"function-calc-no-unspaced-operator": _functionCalcNoUnspacedOperator2["default"], | ||
"function-comma-newline-after": _functionCommaNewlineAfter2["default"], | ||
"function-comma-newline-before": _functionCommaNewlineBefore2["default"], | ||
"function-comma-space-after": _functionCommaSpaceAfter2["default"], | ||
@@ -391,0 +401,0 @@ "function-comma-space-before": _functionCommaSpaceBefore2["default"], |
@@ -63,4 +63,2 @@ "use strict"; | ||
// console.log(JSON.stringify(rule.toString())) | ||
var beforeBrace = (0, _utils.cssStatementStringBeforeBlock)(rule); | ||
@@ -67,0 +65,0 @@ var lineCheckStr = rule.toString().slice(beforeBrace.length); |
@@ -59,3 +59,2 @@ "use strict"; | ||
decimalNumberMatches.forEach(function (match) { | ||
console.log(match); | ||
if (match.sub[1].length <= precision) { | ||
@@ -62,0 +61,0 @@ return; |
@@ -25,2 +25,6 @@ "use strict"; | ||
var _postcssScss = require("postcss-scss"); | ||
var _postcssScss2 = _interopRequireDefault(_postcssScss); | ||
var _plugin = require("./plugin"); | ||
@@ -38,10 +42,12 @@ | ||
var files = _ref.files; | ||
var css = _ref.css; | ||
var code = _ref.code; | ||
var config = _ref.config; | ||
var configBasedir = _ref.configBasedir; | ||
var configOverrides = _ref.configOverrides; | ||
var syntax = _ref.syntax; | ||
var _ref$formatter = _ref.formatter; | ||
var formatter = _ref$formatter === undefined ? "json" : _ref$formatter; | ||
if (!files && !css || files && css) { | ||
throw new Error("You must pass stylelint a `files` glob or a `css` string, though not both"); | ||
if (!files && !code || files && code) { | ||
throw new Error("You must pass stylelint a `files` glob or a `code` string, though not both"); | ||
} | ||
@@ -56,3 +62,3 @@ | ||
var inputReady = files ? (0, _globby2["default"])(files) : Promise.resolve(css); | ||
var inputReady = files ? (0, _globby2["default"])(files) : Promise.resolve(code); | ||
inputReady.then(function (input) { | ||
@@ -84,11 +90,11 @@ var q = (0, _queueAsync2["default"])(); | ||
function lintFile(filepath, cb) { | ||
_fs2["default"].readFile(filepath, "utf8", function (err, css) { | ||
_fs2["default"].readFile(filepath, "utf8", function (err, code) { | ||
if (err) { | ||
reject(err); | ||
} | ||
lint(css, filepath, cb); | ||
lint(code, filepath, cb); | ||
}); | ||
} | ||
function lint(css, filepath, cb) { | ||
function lint(code, filepath, cb) { | ||
var processOptions = {}; | ||
@@ -98,3 +104,6 @@ if (filepath) { | ||
} | ||
(0, _postcss2["default"])().use((0, _plugin2["default"])({ config: config, configBasedir: configBasedir })).process(css, processOptions).then(function (postcssResult) { | ||
if (syntax === "scss") { | ||
processOptions.syntax = _postcssScss2["default"]; | ||
} | ||
(0, _postcss2["default"])().use((0, _plugin2["default"])({ config: config, configBasedir: configBasedir, configOverrides: configOverrides })).process(code, processOptions).then(function (postcssResult) { | ||
var source = !postcssResult.root.source ? undefined : postcssResult.root.source.input.file || postcssResult.root.source.input.id; | ||
@@ -101,0 +110,0 @@ |
@@ -13,2 +13,6 @@ "use strict"; | ||
var _configurationError = require("./configurationError"); | ||
var _configurationError2 = _interopRequireDefault(_configurationError); | ||
var _cssFunctionArguments = require("./cssFunctionArguments"); | ||
@@ -92,2 +96,3 @@ | ||
blurComments: _blurComments2["default"], | ||
configurationError: _configurationError2["default"], | ||
cssFunctionArguments: _cssFunctionArguments2["default"], | ||
@@ -94,0 +99,0 @@ cssStatementBlockString: _cssStatementBlockString2["default"], |
@@ -35,5 +35,11 @@ /** | ||
result.stylelint = result.stylelint || {}; | ||
if (result.stylelint.quiet && result.stylelint.ruleSeverities[ruleName] !== 2) { | ||
return; | ||
} | ||
var startLine = line ? line : node.source && node.source.start.line; | ||
if (result.disabledRanges) { | ||
if (result.stylelint.disabledRanges) { | ||
var _iteratorNormalCompletion = true; | ||
@@ -44,3 +50,3 @@ var _didIteratorError = false; | ||
try { | ||
for (var _iterator = result.disabledRanges[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
for (var _iterator = result.stylelint.disabledRanges[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
var range = _step.value; | ||
@@ -72,12 +78,24 @@ | ||
var warningOpts = node ? { node: node } : {}; | ||
var severity = result.stylelint.ruleSeverities ? result.stylelint.ruleSeverities[ruleName] : 0; | ||
if (!result.stylelint.stylelintError && severity === 2) { | ||
result.stylelint.stylelintError = true; | ||
} | ||
var warningProperties = { | ||
severity: severity, | ||
rule: ruleName | ||
}; | ||
if (node) { | ||
warningProperties.node = node; | ||
} | ||
if (index) { | ||
warningOpts.index = index; | ||
warningProperties.index = index; | ||
} | ||
if (word) { | ||
warningOpts.word = word; | ||
warningProperties.word = word; | ||
} | ||
result.warn(message, warningOpts); | ||
result.warn(message, warningProperties); | ||
}; | ||
module.exports = exports["default"]; |
@@ -19,2 +19,6 @@ "use strict"; | ||
var _configurationError = require("./configurationError"); | ||
var _configurationError2 = _interopRequireDefault(_configurationError); | ||
/** | ||
@@ -123,3 +127,3 @@ * Create a whitespaceChecker, which exposes the following functions: | ||
default: | ||
throw new Error("Unknown expectation \"" + expectation + "\""); | ||
throw (0, _configurationError2["default"])("Unknown expectation \"" + expectation + "\""); | ||
} | ||
@@ -175,3 +179,3 @@ } | ||
default: | ||
throw new Error("Unknown expectation \"" + expectation + "\""); | ||
throw (0, _configurationError2["default"])("Unknown expectation \"" + expectation + "\""); | ||
} | ||
@@ -178,0 +182,0 @@ } |
{ | ||
"name": "stylelint", | ||
"version": "1.2.1", | ||
"version": "2.0.0", | ||
"description": "Modern CSS linter", | ||
@@ -23,2 +23,3 @@ "keywords": [ | ||
"main": "dist/index.js", | ||
"bin": "dist/cli.js", | ||
"files": [ | ||
@@ -34,5 +35,13 @@ "dist", | ||
"execall": "^1.0.0", | ||
"get-stdin": "^5.0.0", | ||
"globby": "^3.0.1", | ||
"lodash": "^3.10.1", | ||
"meow": "^3.3.0", | ||
"postcss": "^5.0.4", | ||
"postcss-selector-parser": "^1.2.0" | ||
"postcss-reporter": "^1.3.0", | ||
"postcss-scss": "^0.1.3", | ||
"postcss-selector-parser": "^1.2.0", | ||
"queue-async": "^1.0.7", | ||
"rc": "^1.1.1", | ||
"resolve-from": "^1.0.1" | ||
}, | ||
@@ -42,3 +51,4 @@ "devDependencies": { | ||
"babel-tape-runner": "^1.2.0", | ||
"eslint": "^1.3.1", | ||
"chalk": "^1.1.1", | ||
"eslint": "^1.4.1", | ||
"eslint-config-stylelint": "^0.1.0", | ||
@@ -50,3 +60,4 @@ "sinon": "^1.16.1", | ||
"scripts": { | ||
"prepublish": "babel src --out-dir dist", | ||
"build": "babel src --out-dir dist", | ||
"prepublish": "npm run build", | ||
"lint": "eslint . --ignore-path .gitignore", | ||
@@ -53,0 +64,0 @@ "tape": "babel-tape-runner \"src/**/__tests__/*.js\"", |
203
README.md
@@ -18,200 +18,19 @@ # stylelint [![NPM version](http://img.shields.io/npm/v/stylelint.svg)](https://www.npmjs.org/package/stylelint) [![Travis Build Status](https://img.shields.io/travis/stylelint/stylelint/master.svg?label=unix%20build)](https://travis-ci.org/stylelint/stylelint) [![AppVeyor Build Status](https://img.shields.io/appveyor/ci/MoOx/stylelint/master.svg?label=windows%20build)](https://ci.appveyor.com/project/MoOx/stylelint) [![Join the chat at https://gitter.im/stylelint/stylelint](https://img.shields.io/badge/gitter%20-join%20chat%20%E2%9E%9E-1dce73.svg)](https://gitter.im/stylelint/stylelint) | ||
## Installation | ||
## Quick start | ||
```console | ||
$ npm install stylelint | ||
``` | ||
With stylelint, it's easy to start linting your CSS to enforce your preferred conventions: | ||
* _PostCSS `5.x` or `4.x` compatibility_ - versions `1.0.0+` of the linter are compatible with PostCSS `5.0.2+`. Whereas, versions `0.8.0 and below` are compatible with PostCSS `4.x`. | ||
* _Use a reporter_ - this plugin registers warnings via PostCSS. Therefore, you'll want to use it with a PostCSS runner that prints warnings (e.g. [`gulp-postcss`](https://github.com/postcss/gulp-postcss)) or another PostCSS plugin that prints warnings (e.g. [`postcss-reporter`](https://github.com/postcss/postcss-reporter)). | ||
1. Install stylelint: `npm install stylelint`. | ||
2. Learn about [some rules](/docs/user-guide/rules.md). No rules are turned on by default, so you only have to learn about the rules you want to enforce; and you can start small, growing your config over time as you have a chance to explore more of the rules. | ||
3. Create your [configuration](/docs/user-guide/configuration.md), probably as a `.stylelintrc` file. | ||
4. Decide whether to use the [CLI](/docs/user-guide/cli.md), [Node API](/docs/user-guide/node-api.md), or [PostCSS plugin](/docs/user-guide/postcss-plugin.md). | ||
5. Lint! | ||
## Usage | ||
## Guides | ||
You must, for now, use the linter as a [PostCSS plugin](https://github.com/postcss/postcss#usage). You can either use a PostCSS runner -- such as [`gulp-postcss`](https://github.com/postcss/gulp-postcss), [`grunt-postcss`](https://github.com/nDmitry/grunt-postcss) and [`postcss-loader`](https://github.com/postcss/postcss-loader) -- or you can use the PostCSS JS API directly. | ||
You'll find more detailed information on using the linter and tailoring it to your needs in our guides: | ||
The linter also _expects a config_. You can either craft your own config or use a [pre-written one](#shareable-configs). | ||
* [User guide](docs/user-guide.md) - Usage, configuration and complementary tools. | ||
* [Developer guide](docs/developer-guide.md) - Contributing to stylelint and writing your own plugins & formatters. | ||
An example of using [`gulp-postcss`](https://github.com/postcss/gulp-postcss) and crafting your own config: | ||
```js | ||
gulp.task("css", function () { | ||
var postcss = require("gulp-postcss") | ||
var stylelint = require("stylelint") | ||
var reporter = require("postcss-reporter") | ||
return gulp.src("src/**/*.css") | ||
.pipe(postcss([ | ||
stylelint({ // an example config that has four rules | ||
"rules": { | ||
"color-no-invalid-hex": 2, | ||
"declaration-colon-space-before": [2, "never"], | ||
"indentation": [2, "tab"], | ||
"number-leading-zero": [2, "always"] | ||
} | ||
}), | ||
reporter({ | ||
clearMessages: true, | ||
}) | ||
])) | ||
}) | ||
``` | ||
An example of using the JS API and the [`stylelint-config-suitcss`](https://github.com/stylelint/stylelint-config-suitcss) config: | ||
```js | ||
var fs = require("fs") | ||
var postcss = require("postcss") | ||
var stylelint = require("stylelint") | ||
var configSuitcss = require("stylelint-config-suitcss") | ||
var reporter = require("postcss-reporter") | ||
// css to be processed | ||
var css = fs.readFileSync("input.css", "utf8") | ||
postcss([ | ||
stylelint(configSuitcss), // using the pre-written SuitCSS config | ||
reporter(), | ||
]) | ||
.process(css, { from: "input.css" }) | ||
.then() | ||
.catch(err => console.error(err.stack)) | ||
``` | ||
### With CSS processors | ||
The linter supports current and future CSS syntax. This includes all standard CSS but also special features that use standard CSS syntactic structures, e.g. special at-rules, special properties, and special functions. Some CSS-*like* language extensions -- features that use non-standard syntactic structures -- are, as such, supported; however, since there are infinite processing possibilities, the linter cannot support everything. | ||
You can run the linter before or after your css processors. Depending on which processors you use, each approach has caveats: | ||
1. *Before*: Some plugins might enable a syntax that isn't compatible with the linter. | ||
2. *After*: Some plugins might generate CSS that is invalid against your linter config, causing warnings that do not correspond to your original stylesheets. | ||
*In both cases you can either turn off the incompatible linter rule, or stop using the incompatible plugin.* You could also approach plugin authors and request alternate formatting options that will make their plugin compatible with stylelint. | ||
### Configuring rules | ||
[Rules](docs/rules.md) are built into the linter and focus on _standard_ CSS. They are configured within the `rules` key of the config. The [user guide](docs/user-guide.md) contains details of how rules are named and how certain ones should be configured in unison. | ||
#### Turning rules on and off | ||
Like [ESLint](http://eslint.org/docs/user-guide/configuring#configuring-rules), each rule can be turned off or on: | ||
* `0` - turn the rule off. | ||
* ~~`1` - turn the rule on as a warning (does not affect exit code).~~ | ||
* `2` - turn the rule on ~~as an error (exit code is 1 when triggered)~~. | ||
Severities are not yet implemented. _See issue [#26](https://github.com/stylelint/stylelint/issues/26) for more details._ | ||
An example of turning one rule off and another on: | ||
```js | ||
{ | ||
"rules": { | ||
"rule-no-single-line": 0, // turn rule off | ||
"declaration-no-important": 2, // turn rule on | ||
} | ||
} | ||
``` | ||
Rules can be temporarily turned off by using special comments in your CSS. For example, you can either turn all the rules off: | ||
```css | ||
/* stylelint-disable */ | ||
a {} | ||
/* stylelint-enable */ | ||
``` | ||
Or you can turn off individual rules: | ||
```css | ||
/* stylelint-disable selector-no-id, declaration-no-important */ | ||
#id { | ||
color: pink !important; | ||
} | ||
/* stylelint-enable */ | ||
``` | ||
#### Configuring options | ||
Only the `*-no-*` rules don't expect options. All the other rules must be explicitly configured as _there are no default values_. | ||
An example of explicitly configuring the options for three rules: | ||
```js | ||
{ | ||
"rules": { | ||
"indentation": [2, "tab", { | ||
except: ["value"], | ||
}], | ||
"declaration-colon-space-before": [2, "never"], | ||
"number-leading-zero": [2, "always"], | ||
} | ||
} | ||
``` | ||
#### Shareable configs | ||
If you prefer to enforce a third-party styleguide (rather than craft your own config), you can use: | ||
* [SuitCSS shareable config](https://github.com/stylelint/stylelint-config-suitcss) | ||
You can also extend a shareable config file, starting with what's there and making your own modifications and additions: | ||
```js | ||
var assign = require("lodash.assign") | ||
var configSuitcss = require("stylelint-config-suitcss") | ||
// change indentation to tabs and turn off the number-leading-zero rule | ||
var myConfig = { | ||
"rules": { | ||
"indentation": [2, "tab"], | ||
"number-leading-zero": 0, | ||
} | ||
} | ||
// merge the configs together | ||
var config = { | ||
rules: assign(configSuitcss.rules, myConfig.rules) | ||
} | ||
``` | ||
### Plugin rules | ||
[Plugins](docs/plugins.md) are userland rules that support _non-standard_ CSS features. To use one, add a `"plugins"` property to your config. The key is the rule's name; the value is the rule function itself. Then, within the `"rules"` object, your can add settings for your plugin rule just like any standard rule. | ||
```js | ||
var myConfig = { | ||
plugins: { | ||
"special-rule": require("special-rule"), | ||
}, | ||
rules: { | ||
// ... | ||
"special-rule": [ 2, "everything" ], | ||
// ... | ||
}, | ||
} | ||
``` | ||
## Complementary tools | ||
The linter works well with: | ||
### Editor plugins | ||
* [linter-stylelint](https://github.com/1000ch/linter-stylelint) - An Atom Linter plugin for stylelint. | ||
* [SublimeLinter-contrib-stylelint](https://github.com/kungfusheep/SublimeLinter-contrib-stylelint) - A Sublime Text plugin for stylelint. | ||
### Other linters & formatters | ||
* [cssfmt](https://github.com/morishitter/cssfmt) - A tool that automatically formats CSS and SCSS source code. | ||
* [postcss-bem-linter](https://github.com/postcss/postcss-bem-linter) - A BEM linter for CSS. | ||
* [stylehacks](https://github.com/ben-eb/stylehacks) - Detect/remove browser hacks from CSS files. | ||
## Requirements | ||
* node@0.12 or io.js@2 | ||
* node@0.10 with [babel-node](http://babeljs.io/docs/usage/cli/#babel-node) | ||
## [Changelog](CHANGELOG.md) | ||
@@ -218,0 +37,0 @@ |
@@ -9,7 +9,9 @@ import { compact } from "lodash" | ||
export default function (root, result) { | ||
result.disabledRanges = [] | ||
result.stylelint = result.stylelint || {} | ||
const disabledRanges = result.stylelint.disabledRanges = [] | ||
let withinDisabledRange = false | ||
result.root.walkComments(comment => { | ||
root.walkComments(comment => { | ||
const { text } = comment | ||
@@ -51,3 +53,3 @@ | ||
result.disabledRanges.push(rangeObj) | ||
disabledRanges.push(rangeObj) | ||
} | ||
@@ -57,4 +59,4 @@ | ||
// Add an `end` prop to the last range | ||
result.disabledRanges[result.disabledRanges.length - 1].end = node.source.end.line | ||
disabledRanges[disabledRanges.length - 1].end = node.source.end.line | ||
} | ||
} |
@@ -9,1 +9,3 @@ module.exports = require("./plugin") | ||
} | ||
module.exports.lint = require("./standalone") |
import postcss from "postcss" | ||
import rc from "rc" | ||
import path from "path" | ||
import resolveFrom from "resolve-from" | ||
import { merge, cloneDeep, isEmpty } from "lodash" | ||
import { configurationError } from "./utils" | ||
import ruleDefinitions from "./rules" | ||
import disableRanges from "./disableRanges" | ||
export default postcss.plugin("stylelint", settings => { | ||
export default postcss.plugin("stylelint", (options = {}) => { | ||
return (root, result) => { | ||
if (!settings) { return } | ||
if (!settings.rules) { return } | ||
// result.stylelint is the namespace for passing stylelint-related | ||
// configuration and data across sub-plugins via the PostCSS Result | ||
result.stylelint = result.stylelint || {} | ||
result.stylelint.ruleSeverities = {} | ||
if (settings.plugins) { | ||
addPluginsToDefinitions(settings.plugins, ruleDefinitions) | ||
let initialConfig = options.hasOwnProperty("config") ? options.config : options | ||
if (isEmpty(initialConfig)) { | ||
initialConfig = rc("stylelint") | ||
} | ||
// First check for disabled ranges | ||
const configBasedir = options.configBasedir || path.dirname(initialConfig.config) | ||
const config = extendConfig(initialConfig, configBasedir) | ||
if (config.plugins) { | ||
Object.keys(config.plugins).forEach(pluginName => { | ||
ruleDefinitions[pluginName] = require(modulePath(config.plugins[pluginName], configBasedir)) | ||
}) | ||
} | ||
if (options.configOverrides) { | ||
merge(config, options.configOverrides) | ||
} | ||
if (!config) { | ||
throw configurationError("No configuration provided") | ||
} | ||
if (!config.rules) { | ||
throw configurationError("No rules found within configuration") | ||
} | ||
// Register details about the configuration | ||
result.stylelint.quiet = config.quiet | ||
// First check for disabled ranges, adding them to the result object | ||
disableRanges(root, result) | ||
Object.keys(settings.rules).forEach(ruleName => { | ||
Object.keys(config.rules).forEach(ruleName => { | ||
if (!ruleDefinitions[ruleName]) { | ||
throw new Error( | ||
`Undefined rule ${ruleName}` | ||
) | ||
throw configurationError(`Undefined rule ${ruleName}`) | ||
} | ||
// If severity is 0, run nothing | ||
const ruleSettings = settings.rules[ruleName] | ||
const ruleSettings = config.rules[ruleName] | ||
const ruleSeverity = (Array.isArray(ruleSettings)) | ||
? ruleSettings[0] | ||
: ruleSettings | ||
if (ruleSeverity === 0) { return } | ||
if (ruleSeverity === 0) { | ||
return | ||
} | ||
// Otherwise, run the rule with the primary and secondary options | ||
// Log the rule's severity | ||
result.stylelint.ruleSeverities[ruleName] = ruleSeverity | ||
// Run the rule with the primary and secondary options | ||
ruleDefinitions[ruleName](ruleSettings[1], ruleSettings[2])(root, result) | ||
@@ -37,10 +71,24 @@ }) | ||
function addPluginsToDefinitions(plugins, definitions) { | ||
Object.keys(plugins).forEach(name => { | ||
if (typeof plugins[name] !== "function") { | ||
addPluginsToDefinitions(plugins[name], definitions) | ||
return | ||
} | ||
definitions[name] = plugins[name] | ||
}) | ||
function extendConfig(config, configBasedir) { | ||
if (!config.extends) { return config } | ||
return [].concat(config.extends).reduce((mergedConfig, extendingConfigLookup) => { | ||
let extendingConfigPath = modulePath(extendingConfigLookup, configBasedir || process.cwd()) | ||
// Now we must recursively extend the extending config | ||
let extendingConfig = extendConfig(require(extendingConfigPath), path.dirname(extendingConfigPath)) | ||
return merge({}, extendingConfig, mergedConfig) | ||
}, cloneDeep(config)) | ||
} | ||
function modulePath(lookup, basedir) { | ||
try { | ||
return resolveFrom(basedir, lookup) | ||
} catch (e) { | ||
throw configurationError( | ||
`Could not find "${lookup}". ` + | ||
`Do you need a \`configBasedir\`?` | ||
) | ||
} | ||
} |
@@ -14,2 +14,4 @@ import { | ||
rejectedAfter: () => `Unexpected whitespace after ","`, | ||
expectedAfterSingleLine: () => `Expected single space after "," in a single-line list`, | ||
rejectedAfterSingleLine: () => `Unexpected whitespace after "," in a single-line list`, | ||
}) | ||
@@ -25,2 +27,4 @@ | ||
"never", | ||
"always-single-line", | ||
"never-single-line", | ||
], | ||
@@ -27,0 +31,0 @@ }) |
@@ -13,3 +13,3 @@ # function-comma-space-after | ||
`string`: `"always"|"never"` | ||
`string`: `"always"|"never"|"always-single-line"|"never-single-line"` | ||
@@ -63,1 +63,63 @@ ### `"always"` | ||
``` | ||
### `"always-single-line"` | ||
There *must always* be a single space after the commas in single-line functions. | ||
The following patterns are considered warnings: | ||
```css | ||
a { transform: translate(1,1) } | ||
``` | ||
```css | ||
a { transform: translate(1 ,1) } | ||
``` | ||
The following patterns are *not* considered warnings: | ||
```css | ||
a { transform: translate(1, 1) } | ||
``` | ||
```css | ||
a { transform: translate(1 , 1) } | ||
``` | ||
```css | ||
a { | ||
transform: translate(1 | ||
,1) | ||
} | ||
``` | ||
### `"never-single-line"` | ||
There *must never* be whitepace after the commas in single-line functions. | ||
The following patterns are considered warnings: | ||
```css | ||
a { transform: translate(1, 1) } | ||
``` | ||
```css | ||
a { transform: translate(1 , 1) } | ||
``` | ||
The following patterns are *not* considered warnings: | ||
```css | ||
a { transform: translate(1,1) } | ||
``` | ||
```css | ||
a { transform: translate(1 ,1) } | ||
``` | ||
```css | ||
a { | ||
transform: translate(1 | ||
, 1) | ||
} | ||
``` |
@@ -13,2 +13,4 @@ import { | ||
rejectedBefore: () => `Unexpected whitespace before ","`, | ||
expectedBeforeSingleLine: () => `Expected single space before "," in a single-line list`, | ||
rejectedBeforeSingleLine: () => `Unexpected whitespace before "," in a single-line list`, | ||
}) | ||
@@ -24,2 +26,4 @@ | ||
"never", | ||
"always-single-line", | ||
"never-single-line", | ||
], | ||
@@ -26,0 +30,0 @@ }) |
@@ -13,3 +13,3 @@ # function-comma-space-before | ||
`string`: `"always"|"never"` | ||
`string`: `"always"|"never"|"always-single-line"|"never-single-line"` | ||
@@ -63,1 +63,63 @@ ### `"always"` | ||
``` | ||
### `"always-single-line"` | ||
There *must always* be a single space before the commas in single-line functions. | ||
The following patterns are considered warnings: | ||
```css | ||
a { transform: translate(1,1) } | ||
``` | ||
```css | ||
a { transform: translate(1, 1) } | ||
``` | ||
The following patterns are *not* considered warnings: | ||
```css | ||
a { transform: translate(1 ,1) } | ||
``` | ||
```css | ||
a { transform: translate(1 , 1) } | ||
``` | ||
```css | ||
a { | ||
transform: translate(1, | ||
1) | ||
} | ||
``` | ||
### `"never-single-line"` | ||
There *must never* be whitepace before the commas in single-line functions. | ||
The following patterns are considered warnings: | ||
```css | ||
a { transform: translate(1 ,1) } | ||
``` | ||
```css | ||
a { transform: translate(1 , 1) } | ||
``` | ||
The following patterns are *not* considered warnings: | ||
```css | ||
a { transform: translate(1,1) } | ||
``` | ||
```css | ||
a { transform: translate(1, 1) } | ||
``` | ||
```css | ||
a { | ||
transform: translate(1 , | ||
1) | ||
} | ||
``` |
@@ -31,2 +31,4 @@ import atRuleEmptyLineBefore from "./at-rule-empty-line-before" | ||
import functionCalcNoUnspacedOperator from "./function-calc-no-unspaced-operator" | ||
import functionCommaNewlineAfter from "./function-comma-newline-after" | ||
import functionCommaNewlineBefore from "./function-comma-newline-before" | ||
import functionCommaSpaceAfter from "./function-comma-space-after" | ||
@@ -121,2 +123,4 @@ import functionCommaSpaceBefore from "./function-comma-space-before" | ||
"function-calc-no-unspaced-operator": functionCalcNoUnspacedOperator, | ||
"function-comma-newline-after": functionCommaNewlineAfter, | ||
"function-comma-newline-before": functionCommaNewlineBefore, | ||
"function-comma-space-after": functionCommaSpaceAfter, | ||
@@ -123,0 +127,0 @@ "function-comma-space-before": functionCommaSpaceBefore, |
@@ -48,4 +48,2 @@ import { | ||
// console.log(JSON.stringify(rule.toString())) | ||
const beforeBrace = cssStatementStringBeforeBlock(rule) | ||
@@ -52,0 +50,0 @@ const lineCheckStr = rule.toString().slice(beforeBrace.length) |
@@ -45,3 +45,2 @@ import { isNumber } from "lodash" | ||
decimalNumberMatches.forEach(match => { | ||
console.log(match) | ||
if (match.sub[1].length <= precision) { return } | ||
@@ -48,0 +47,0 @@ report({ |
import blurComments from "./blurComments" | ||
import configurationError from "./configurationError" | ||
import cssFunctionArguments from "./cssFunctionArguments" | ||
@@ -24,2 +25,3 @@ import cssStatementBlockString from "./cssStatementBlockString" | ||
blurComments, | ||
configurationError, | ||
cssFunctionArguments, | ||
@@ -26,0 +28,0 @@ cssStatementBlockString, |
@@ -21,3 +21,8 @@ /** | ||
export default function ({ ruleName, result, message, line, node, index, word }) { | ||
result.stylelint = result.stylelint || {} | ||
if (result.stylelint.quiet && result.stylelint.ruleSeverities[ruleName] !== 2) { | ||
return | ||
} | ||
const startLine = (line) | ||
@@ -27,4 +32,4 @@ ? line | ||
if (result.disabledRanges) { | ||
for (let range of result.disabledRanges) { | ||
if (result.stylelint.disabledRanges) { | ||
for (let range of result.stylelint.disabledRanges) { | ||
if ( | ||
@@ -40,10 +45,16 @@ // If the violation is within a disabledRange, | ||
const warningOpts = (node) ? { node } : {} | ||
if (index) { | ||
warningOpts.index = index | ||
const severity = (result.stylelint.ruleSeverities) ? result.stylelint.ruleSeverities[ruleName] : 0 | ||
if (!result.stylelint.stylelintError && severity === 2) { | ||
result.stylelint.stylelintError = true | ||
} | ||
if (word) { | ||
warningOpts.word = word | ||
const warningProperties = { | ||
severity, | ||
rule: ruleName, | ||
} | ||
result.warn(message, warningOpts) | ||
if (node) { warningProperties.node = node } | ||
if (index) { warningProperties.index = index } | ||
if (word) { warningProperties.word = word } | ||
result.warn(message, warningProperties) | ||
} |
import { assign } from "lodash" | ||
import isWhitespace from "./isWhitespace" | ||
import isSingleLineString from "./isSingleLineString" | ||
import configurationError from "./configurationError" | ||
@@ -97,3 +98,3 @@ /** | ||
default: | ||
throw new Error(`Unknown expectation "${expectation}"`) | ||
throw configurationError(`Unknown expectation "${expectation}"`) | ||
} | ||
@@ -134,3 +135,3 @@ } | ||
default: | ||
throw new Error(`Unknown expectation "${expectation}"`) | ||
throw configurationError(`Unknown expectation "${expectation}"`) | ||
} | ||
@@ -137,0 +138,0 @@ } |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
559199
337
12319
14
8
40
9
+ Addedget-stdin@^5.0.0
+ Addedglobby@^3.0.1
+ Addedmeow@^3.3.0
+ Addedpostcss-reporter@^1.3.0
+ Addedpostcss-scss@^0.1.3
+ Addedqueue-async@^1.0.7
+ Addedrc@^1.1.1
+ Addedresolve-from@^1.0.1
+ Addedarray-find-index@1.0.2(transitive)
+ Addedarray-union@1.0.2(transitive)
+ Addedarray-uniq@1.0.3(transitive)
+ Addedarrify@1.0.1(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbrace-expansion@1.1.11(transitive)
+ Addedcamelcase@2.1.1(transitive)
+ Addedcamelcase-keys@2.1.0(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addedcurrently-unhandled@0.4.1(transitive)
+ Addeddecamelize@1.2.0(transitive)
+ Addeddeep-extend@0.6.0(transitive)
+ Addederror-ex@1.3.2(transitive)
+ Addedfind-up@1.1.2(transitive)
+ Addedfunction-bind@1.1.2(transitive)
+ Addedget-stdin@4.0.15.0.1(transitive)
+ Addedglob@5.0.15(transitive)
+ Addedglobby@3.0.1(transitive)
+ Addedgraceful-fs@4.2.11(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedhosted-git-info@2.8.9(transitive)
+ Addedindent-string@2.1.0(transitive)
+ Addedinflight@1.0.6(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedini@1.3.8(transitive)
+ Addedis-arrayish@0.2.1(transitive)
+ Addedis-core-module@2.15.1(transitive)
+ Addedis-finite@1.1.0(transitive)
+ Addedis-utf8@0.2.1(transitive)
+ Addedload-json-file@1.1.0(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedlog-symbols@1.0.2(transitive)
+ Addedloud-rejection@1.6.0(transitive)
+ Addedmap-obj@1.0.1(transitive)
+ Addedmeow@3.7.0(transitive)
+ Addedminimatch@3.1.2(transitive)
+ Addedminimist@1.2.8(transitive)
+ Addednormalize-package-data@2.5.0(transitive)
+ Addedobject-assign@4.1.1(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedparse-json@2.2.0(transitive)
+ Addedpath-exists@2.1.0(transitive)
+ Addedpath-is-absolute@1.0.1(transitive)
+ Addedpath-parse@1.0.7(transitive)
+ Addedpath-type@1.1.0(transitive)
+ Addedpify@2.3.0(transitive)
+ Addedpinkie@1.0.02.0.4(transitive)
+ Addedpinkie-promise@1.0.02.0.1(transitive)
+ Addedpostcss-reporter@1.4.1(transitive)
+ Addedpostcss-scss@0.1.9(transitive)
+ Addedqueue-async@1.2.1(transitive)
+ Addedrc@1.2.8(transitive)
+ Addedread-pkg@1.1.0(transitive)
+ Addedread-pkg-up@1.0.1(transitive)
+ Addedredent@1.0.0(transitive)
+ Addedrepeating@2.0.1(transitive)
+ Addedresolve@1.22.8(transitive)
+ Addedresolve-from@1.0.1(transitive)
+ Addedsemver@5.7.2(transitive)
+ Addedsignal-exit@3.0.7(transitive)
+ Addedspdx-correct@3.2.0(transitive)
+ Addedspdx-exceptions@2.5.0(transitive)
+ Addedspdx-expression-parse@3.0.1(transitive)
+ Addedspdx-license-ids@3.0.20(transitive)
+ Addedstrip-bom@2.0.0(transitive)
+ Addedstrip-indent@1.0.1(transitive)
+ Addedstrip-json-comments@2.0.1(transitive)
+ Addedsupports-preserve-symlinks-flag@1.0.0(transitive)
+ Addedtrim-newlines@1.0.0(transitive)
+ Addedvalidate-npm-package-license@3.0.4(transitive)
+ Addedwrappy@1.0.2(transitive)