Initial commit

This commit is contained in:
Patrick Marsceill
2017-03-09 13:16:08 -05:00
commit b7b0d0d7bf
4147 changed files with 401224 additions and 0 deletions

6
node_modules/stylelint/lib/alwaysIgnoredGlobs.js generated vendored Normal file
View File

@@ -0,0 +1,6 @@
/* @flow */
"use strict"
const alwaysIgnoredGlobs/*: Array<string>*/ = [ "**/node_modules/**", "**/bower_components/**" ]
module.exports = alwaysIgnoredGlobs

181
node_modules/stylelint/lib/assignDisabledRanges.js generated vendored Normal file
View File

@@ -0,0 +1,181 @@
/* @flow */
"use strict"
const _ = require("lodash")
const COMMAND_PREFIX = "stylelint-"
const disableCommand = COMMAND_PREFIX + "disable"
const enableCommand = COMMAND_PREFIX + "enable"
const disableLineCommand = COMMAND_PREFIX + "disable-line"
const disableNextLineCommand = COMMAND_PREFIX + "disable-next-line"
const ALL_RULES = "all"
/*:: type disabledRangeObject = {
[ruleName: string]: Array<{
start: number,
end?: number,
}>
}*/
// Run it like a plugin ...
module.exports = function (
root/*: Object*/,
result/*: Object*/
) {
result.stylelint = result.stylelint || {}
// Most of the functions below work via side effects mutating
// this object
const disabledRanges/*: disabledRangeObject*/ = {
all: [],
}
result.stylelint.disabledRanges = disabledRanges
root.walkComments(checkComment)
return result
function processDisableLineCommand(comment/*: postcss$comment*/) {
getCommandRules(disableLineCommand, comment.text).forEach(ruleName => {
disableLine(comment.source.start.line, ruleName, comment)
})
}
function processDisableNextLineCommand(comment/*: postcss$comment*/) {
getCommandRules(disableNextLineCommand, comment.text).forEach(ruleName => {
disableLine(comment.source.start.line + 1, ruleName, comment)
})
}
function disableLine(
line/*: number*/,
ruleName/*: string*/,
comment/*: postcss$comment*/
) {
if (ruleIsDisabled(ALL_RULES)) {
throw comment.error("All rules have already been disabled", { plugin: "stylelint" })
}
if (ruleIsDisabled(ruleName)) {
throw comment.error(`"${ruleName}" has already been disabled`, { plugin: "stylelint" })
}
if (ruleName === ALL_RULES) {
Object.keys(disabledRanges).forEach(disabledRuleName => {
startDisabledRange(line, disabledRuleName)
endDisabledRange(line, disabledRuleName)
})
} else {
startDisabledRange(line, ruleName)
endDisabledRange(line, ruleName)
}
}
function processDisableCommand(comment/*: postcss$comment*/) {
getCommandRules(disableCommand, comment.text).forEach(ruleToDisable => {
if (ruleToDisable === ALL_RULES) {
if (ruleIsDisabled(ALL_RULES)) {
throw comment.error("All rules have already been disabled", { plugin: "stylelint" })
}
Object.keys(disabledRanges).forEach(ruleName => {
startDisabledRange(comment.source.start.line, ruleName)
})
return
}
if (ruleIsDisabled(ruleToDisable)) {
throw comment.error(`"${ruleToDisable}" has already been disabled`, { plugin: "stylelint" })
}
startDisabledRange(comment.source.start.line, ruleToDisable)
})
}
function processEnableCommand(comment/*: postcss$comment*/) {
getCommandRules(enableCommand, comment.text).forEach(ruleToEnable => {
if (ruleToEnable === ALL_RULES) {
if (_.values(disabledRanges).every(ranges => _.isEmpty(ranges) || !!_.last(ranges.end))) {
throw comment.error("No rules have been disabled", { plugin: "stylelint" })
}
Object.keys(disabledRanges).forEach(ruleName => {
if (!_.get(_.last(disabledRanges[ruleName]), "end")) {
endDisabledRange(comment.source.end.line, ruleName)
}
})
return
}
if (ruleIsDisabled(ALL_RULES) && disabledRanges[ruleToEnable] === undefined) {
// Get a starting point from the where all rules were disabled
if (!disabledRanges[ruleToEnable]) {
disabledRanges[ruleToEnable] = _.cloneDeep(disabledRanges.all)
} else {
disabledRanges[ruleToEnable].push(_.clone(_.last(disabledRanges[ALL_RULES])))
}
endDisabledRange(comment.source.end.line, ruleToEnable)
return
}
if (ruleIsDisabled(ruleToEnable)) {
endDisabledRange(comment.source.end.line, ruleToEnable)
return
}
throw comment.error(`"${ruleToEnable}" has not been disabled`, { plugin: "stylelint" })
})
}
function checkComment(comment/*: postcss$comment*/) {
const text = comment.text
// Ignore comments that are not relevant commands
if (text.indexOf(COMMAND_PREFIX) !== 0) {
return result
}
if (text.indexOf(disableLineCommand) === 0) {
processDisableLineCommand(comment)
} else if (text.indexOf(disableNextLineCommand) === 0) {
processDisableNextLineCommand(comment)
} else if (text.indexOf(disableCommand) === 0) {
processDisableCommand(comment)
} else if (text.indexOf(enableCommand) === 0) {
processEnableCommand(comment)
}
}
function getCommandRules(
command/*: string*/,
fullText/*: string*/
)/*: Array<string>*/ {
const rules = _.compact(fullText.slice(command.length).split(",")).map(r => r.trim())
if (_.isEmpty(rules)) {
return [ALL_RULES]
}
return rules
}
function startDisabledRange(line/*: number*/, ruleName/*: string*/) {
const rangeObj = { start: line }
ensureRuleRanges(ruleName)
disabledRanges[ruleName].push(rangeObj)
}
function endDisabledRange(line/*: number*/, ruleName/*: string*/) {
const lastRangeForRule = _.last(disabledRanges[ruleName])
if (!lastRangeForRule) {
return
}
// Add an `end` prop to the last range of that rule
lastRangeForRule.end = line
}
function ensureRuleRanges(ruleName/*: string*/) {
if (!disabledRanges[ruleName]) {
disabledRanges[ruleName] = _.cloneDeep(disabledRanges.all)
}
}
function ruleIsDisabled(ruleName/*: string*/)/*: boolean*/ {
if (disabledRanges[ruleName] === undefined) return false
if (_.last(disabledRanges[ruleName]) === undefined) return false
if (_.get(_.last(disabledRanges[ruleName]), "end") === undefined) return true
return false
}
}

332
node_modules/stylelint/lib/augmentConfig.js generated vendored Normal file
View File

@@ -0,0 +1,332 @@
/* @flow */
"use strict"
const configurationError = require("./utils/configurationError")
const getModulePath = require("./utils/getModulePath")
const _ = require("lodash")
const fs = require("fs")
const globjoin = require("globjoin")
const normalizeRuleSettings = require("./normalizeRuleSettings")
const path = require("path")
const rules = require("./rules")
const DEFAULT_IGNORE_FILENAME = ".stylelintignore"
const FILE_NOT_FOUND_ERROR_CODE = "ENOENT"
// - Merges config and configOverrides
// - Makes all paths absolute
// - Merges extends
function augmentConfigBasic(
stylelint/*: stylelint$internalApi*/,
config/*: stylelint$config*/,
configDir/*: string*/,
allowOverrides/*:: ?: boolean*/
)/*: Promise<stylelint$config>*/ {
return Promise.resolve().then(() => {
if (!allowOverrides) return config
return _.merge(config, stylelint._options.configOverrides)
}).then(augmentedConfig => {
return extendConfig(stylelint, augmentedConfig, configDir)
}).then(augmentedConfig => {
return absolutizePaths(augmentedConfig, configDir)
})
}
// Extended configs need to be run through augmentConfigBasic
// but do not need the full treatment. Things like pluginFunctions
// will be resolved and added by the parent config.
function augmentConfigExtended(
stylelint/*: stylelint$internalApi*/,
cosmiconfigResultArg/*: ?{
config: stylelint$config,
filepath: string,
}*/
)/*: Promise<?{ config: stylelint$config, filepath: string }>*/ {
const cosmiconfigResult = cosmiconfigResultArg // Lock in for Flow
if (!cosmiconfigResult) return Promise.resolve(null)
const configDir = path.dirname(cosmiconfigResult.filepath || "")
const cleanedConfig = _.omit(cosmiconfigResult.config, "ignoreFiles")
return augmentConfigBasic(stylelint, cleanedConfig, configDir).then(augmentedConfig => {
return {
config: augmentedConfig,
filepath: cosmiconfigResult.filepath,
}
})
}
function augmentConfigFull(
stylelint/*: stylelint$internalApi*/,
cosmiconfigResultArg/*: ?{
config: stylelint$config,
filepath: string,
}*/
)/*: Promise<?{ config: stylelint$config, filepath: string }>*/ {
const cosmiconfigResult = cosmiconfigResultArg // Lock in for Flow
if (!cosmiconfigResult) return Promise.resolve(null)
const config = cosmiconfigResult.config,
filepath = cosmiconfigResult.filepath
const configDir = stylelint._options.configBasedir || path.dirname(filepath || "")
return augmentConfigBasic(stylelint, config, configDir, true).then(augmentedConfig => {
return addIgnorePatterns(stylelint, augmentedConfig)
}).then(augmentedConfig => {
return addPluginFunctions(augmentedConfig)
}).then(augmentedConfig => {
return addProcessorFunctions(augmentedConfig)
}).then(augmentedConfig => {
if (!augmentedConfig.rules) {
throw configurationError("No rules found within configuration. Have you provided a \"rules\" property?")
}
return normalizeAllRuleSettings(augmentedConfig)
}).then(augmentedConfig => {
return {
config: augmentedConfig,
filepath: cosmiconfigResult.filepath,
}
})
}
// Load a file ignore ignore patterns, if there is one;
// then add them to the config as an ignorePatterns property
function addIgnorePatterns(
stylelint/*: stylelint$internalApi*/,
config/*: stylelint$config*/
)/*: Promise<stylelint$config>*/ {
const ignoreFilePath = stylelint._options.ignorePath || DEFAULT_IGNORE_FILENAME
const absoluteIgnoreFilePath = path.isAbsolute(ignoreFilePath) ? ignoreFilePath : path.resolve(process.cwd(), ignoreFilePath)
return new Promise((resolve, reject) => {
fs.readFile(absoluteIgnoreFilePath, "utf8", (err, data) => {
if (err) {
// If the file's not found, fine, we'll just
// consider it an empty array of globs
if (err.code === FILE_NOT_FOUND_ERROR_CODE) {
return resolve(config)
}
return reject(err)
}
// Add an ignorePatterns property to the config, containing the
// .gitignore-patterned globs loaded from .stylelintignore
const augmentedConfig/*: stylelint$config*/ = Object.assign({}, config, {
ignorePatterns: data,
})
resolve(augmentedConfig)
})
})
}
// Make all paths in the config absolute:
// - ignoreFiles
// - plugins
// - processors
// (extends handled elsewhere)
function absolutizePaths(
config/*: stylelint$config*/,
configDir/*: string*/
)/*: stylelint$config*/ {
if (config.ignoreFiles) {
config.ignoreFiles = [].concat(config.ignoreFiles).map(glob => {
if (path.isAbsolute(glob.replace(/^!/, ""))) return glob
return globjoin(configDir, glob)
})
}
if (config.plugins) {
config.plugins = [].concat(config.plugins).map(lookup => {
return getModulePath(configDir, lookup)
})
}
if (config.processors) {
config.processors = absolutizeProcessors(config.processors, configDir)
}
return config
}
// Processors are absolutized in their own way because
// they can be and return a string or an array
function absolutizeProcessors(
processors/*: stylelint$configProcessors*/,
configDir/*: string*/
)/*: stylelint$configProcessors*/ {
const normalizedProcessors = Array.isArray(processors) ? processors : [processors]
return normalizedProcessors.map(item => {
if (typeof item === "string") {
return getModulePath(configDir, item)
}
return [ getModulePath(configDir, item[0]), item[1] ]
})
}
function extendConfig(
stylelint/*: stylelint$internalApi*/,
config/*: stylelint$config*/,
configDir/*: string*/
)/*: Promise<stylelint$config>*/ {
if (config.extends === undefined) return Promise.resolve(config)
const normalizedExtends = Array.isArray(config.extends) ? config.extends : [config.extends]
const originalWithoutExtends = _.omit(config, "extends")
const loadExtends = normalizedExtends.reduce((resultPromise, extendLookup) => {
return resultPromise.then(resultConfig => {
return loadExtendedConfig(stylelint, resultConfig, configDir, extendLookup).then(extendResult => {
if (!extendResult) return resultConfig
return mergeConfigs(resultConfig, extendResult.config)
})
})
}, Promise.resolve(originalWithoutExtends))
return loadExtends.then(resultConfig => {
return mergeConfigs(resultConfig, originalWithoutExtends)
})
}
function loadExtendedConfig(
stylelint/*: stylelint$internalApi*/,
config/*: stylelint$config*/,
configDir/*: string*/,
extendLookup/*: string*/
)/*: Promise<?{ config: stylelint$config, filepath: string }>*/ {
const extendPath = getModulePath(configDir, extendLookup)
return stylelint._extendExplorer.load(null, extendPath)
}
// When merging configs (via extends)
// - plugin and processor arrays are joined
// - rules are merged via Object.assign, so there is no attempt made to
// merge any given rule's settings. If b contains the same rule as a,
// b's rule settings will override a's rule settings entirely.
// - Everything else is merged via Object.assign
function mergeConfigs(a/*: stylelint$config*/, b/*: stylelint$config*/)/*: stylelint$config*/ {
const pluginMerger = {}
if (a.plugins || b.plugins) {
pluginMerger.plugins = []
if (a.plugins) {
pluginMerger.plugins = pluginMerger.plugins.concat(a.plugins)
}
if (b.plugins) {
pluginMerger.plugins = _.uniq(pluginMerger.plugins.concat(b.plugins))
}
}
const processorMerger = {}
if (a.processors || b.processors) {
processorMerger.processors = []
if (a.processors) {
processorMerger.processors = processorMerger.processors.concat(a.processors)
}
if (b.processors) {
processorMerger.processors = _.uniq(processorMerger.processors.concat(b.processors))
}
}
const rulesMerger = {}
if (a.rules || b.rules) {
rulesMerger.rules = Object.assign({}, a.rules, b.rules)
}
const result = Object.assign({}, a, b, processorMerger, pluginMerger, rulesMerger)
return result
}
function addPluginFunctions(config/*: stylelint$config*/)/*: stylelint$config*/ {
if (!config.plugins) return config
const normalizedPlugins = Array.isArray(config.plugins) ? config.plugins : [config.plugins]
const pluginFunctions = normalizedPlugins.reduce((result, pluginLookup) => {
let pluginImport = require(pluginLookup)
// Handle either ES6 or CommonJS modules
pluginImport = pluginImport.default || pluginImport
// A plugin can export either a single rule definition
// or an array of them
const normalizedPluginImport = Array.isArray(pluginImport) ? pluginImport : [pluginImport]
normalizedPluginImport.forEach(pluginRuleDefinition => {
if (!pluginRuleDefinition.ruleName) {
throw configurationError("stylelint v3+ requires plugins to expose a ruleName. " + `The plugin "${pluginLookup}" is not doing this, so will not work ` + "with stylelint v3+. Please file an issue with the plugin.")
}
if (!_.includes(pluginRuleDefinition.ruleName, "/")) {
throw configurationError("stylelint v7+ requires plugin rules to be namspaced, " + "i.e. only `plugin-namespace/plugin-rule-name` plugin rule names are supported. " + `The plugin rule "${pluginRuleDefinition.ruleName}" does not do this, so will not work. ` + "Please file an issue with the plugin.")
}
result[pluginRuleDefinition.ruleName] = pluginRuleDefinition.rule
})
return result
}, {})
config.pluginFunctions = pluginFunctions
return config
}
function normalizeAllRuleSettings(config/*: stylelint$config*/)/*: stylelint$config*/ {
const normalizedRules = {}
if (!config.rules) return config
Object.keys(config.rules).forEach(ruleName => {
const rawRuleSettings = _.get(config, [ "rules", ruleName ])
const rule = rules[ruleName] || _.get(config, [ "pluginFunctions", ruleName ])
if (!rule) {
throw configurationError(`Undefined rule ${ruleName}`)
}
normalizedRules[ruleName] = normalizeRuleSettings(rawRuleSettings, ruleName, _.get(rule, "primaryOptionArray"))
})
config.rules = normalizedRules
return config
}
// Given an array of processors strings, we want to add two
// properties to the augmented config:
// - codeProcessors: functions that will run on code as it comes in
// - resultProcessors: functions that will run on results as they go out
//
// To create these properties, we need to:
// - Find the processor module
// - Intialize the processor module by calling its functions with any
// provided options
// - Push the processor's code and result processors to their respective arrays
const processorCache = new Map()
function addProcessorFunctions(config/*: stylelint$config*/)/*: stylelint$config*/ {
if (!config.processors) return config
const codeProcessors = []
const resultProcessors = []
;[].concat(config.processors).forEach(processorConfig => {
const processorKey = JSON.stringify(processorConfig)
let initializedProcessor
if (processorCache.has(processorKey)) {
initializedProcessor = processorCache.get(processorKey)
} else {
processorConfig = [].concat(processorConfig)
const processorLookup = processorConfig[0]
const processorOptions = processorConfig[1]
let processor = require(processorLookup)
processor = processor.default || processor
initializedProcessor = processor(processorOptions)
processorCache.set(processorKey, initializedProcessor)
}
if (initializedProcessor && initializedProcessor.code) {
codeProcessors.push(initializedProcessor.code)
}
if (initializedProcessor && initializedProcessor.result) {
resultProcessors.push(initializedProcessor.result)
}
})
config.codeProcessors = codeProcessors
config.resultProcessors = resultProcessors
return config
}
module.exports = { augmentConfigExtended, augmentConfigFull }

228
node_modules/stylelint/lib/cli.js generated vendored Normal file
View File

@@ -0,0 +1,228 @@
#!/usr/bin/env node
/* @flow */
"use strict"
const getModulePath = require("./utils/getModulePath")
const getStdin = require("get-stdin")
const meow = require("meow")
const needlessDisablesStringFormatter = require("./formatters/needlessDisablesStringFormatter")
const path = require("path")
const resolveFrom = require("resolve-from")
const standalone = require("./standalone")
const minimistOptions = {
default: {
config: false,
f: "string",
q: false,
},
alias: {
f: "formatter",
h: "help",
i: "ignore-path",
id: "ignore-disables",
q: "quiet",
rd: "report-needless-disables",
s: "syntax",
v: "version",
aei: "allow-empty-input",
},
boolean: [
"allow-empty-input",
"color",
"help",
"ignore-disables",
"no-color",
"quiet",
"version",
],
}
const meowOptions = {
help: `
Usage: stylelint [input] [options]
Input: Files(s), glob(s), or nothing to use stdin.
If an input argument is wrapped in quotation marks, it will be passed to
node-glob for cross-platform glob support. node_modules and
bower_components are always ignored. You can also pass no input and use
stdin, instead.
Options:
--config
Path to a specific configuration file (JSON, YAML, or CommonJS), or the
name of a module in node_modules that points to one. If no --config
argument is provided, stylelint will search for configuration files in
the following places, in this order:
- a stylelint property in package.json
- a .stylelintrc file (with or without filename extension:
.json, .yaml, and .js are available)
- a stylelint.config.js file exporting a JS object
The search will begin in the working directory and move up the directory
tree until a configuration file is found.
--config-basedir
An absolute path to the directory that relative paths defining "extends"
and "plugins" are *relative to*. Only necessary if these values are
relative paths.
--ignore-path, -i
Path to a file containing patterns that describe files to ignore. The
path can be absolute or relative to process.cwd(). By default, stylelint
looks for .stylelintignore in process.cwd().
--syntax, -s
Specify a non-standard syntax. Options: "scss", "less", "sugarss".
If you do not specify a syntax, non-standard syntaxes will be
automatically inferred by the file extensions .scss, .less, and .sss.
--custom-syntax
Module name or path to a JS file exporting a PostCSS-compatible syntax.
--stdin-filename
A filename to assign stdin input.
--ignore-disables, --id
Ignore styleline-disable comments.
--formatter, -f [default: "string"]
The output formatter: "json", "string" or "verbose".
--custom-formatter
Path to a JS file exporting a custom formatting function.
--quiet, -q
Only register warnings for rules with an "error"-level severity (ignore
"warning"-level).
--color
--no-color
Force enabling/disabling of color.
--allow-empty-input, -aei
If no files match glob pattern, exits without throwing an error.
--report-needless-disables, --rd
Report stylelint-disable comments that are not blocking a lint warning.
If you provide the argument "error", the process will exit with code 2
if needless disables are found.
--version, -v
Show the currently installed version of stylelint.
`,
pkg: "../package.json",
}
const cli = meow(meowOptions, minimistOptions)
let formatter = cli.flags.formatter
if (cli.flags.customFormatter) {
const customFormatter = path.isAbsolute(cli.flags.customFormatter) ? cli.flags.customFormatter : path.join(process.cwd(), cli.flags.customFormatter)
formatter = require(customFormatter)
}
const optionsBase/*: Object*/ = {
formatter,
configOverrides: {},
}
if (cli.flags.quiet) {
optionsBase.configOverrides.quiet = cli.flags.quiet
}
if (cli.flags.syntax) {
optionsBase.syntax = cli.flags.syntax
}
if (cli.flags.customSyntax) {
optionsBase.customSyntax = getModulePath(process.cwd(), cli.flags.customSyntax)
}
if (cli.flags.config) {
// Should check these possibilities:
// a. name of a node_module
// b. absolute path
// c. relative path relative to `process.cwd()`.
// If none of the above work, we'll try a relative path starting
// in `process.cwd()`.
optionsBase.configFile = resolveFrom(process.cwd(), cli.flags.config) || path.join(process.cwd(), cli.flags.config)
}
if (cli.flags.configBasedir) {
optionsBase.configBasedir = path.isAbsolute(cli.flags.configBasedir) ? cli.flags.configBasedir : path.resolve(process.cwd(), cli.flags.configBasedir)
}
if (cli.flags.stdinFilename) {
optionsBase.codeFilename = cli.flags.stdinFilename
}
if (cli.flags.ignorePath) {
optionsBase.ignorePath = cli.flags.ignorePath
}
if (cli.flags.ignoreDisables) {
optionsBase.ignoreDisables = cli.flags.ignoreDisables
}
if (cli.flags.allowEmptyInput) {
optionsBase.allowEmptyInput = cli.flags.allowEmptyInput
}
const reportNeedlessDisables = cli.flags.reportNeedlessDisables
if (reportNeedlessDisables) {
optionsBase.reportNeedlessDisables = reportNeedlessDisables
}
Promise.resolve().then(() => {
// Add input/code into options
if (cli.input.length) {
return Object.assign({}, optionsBase, {
files: cli.input,
})
}
return getStdin().then(stdin => Object.assign({}, optionsBase, {
code: stdin,
}))
}).then(options => {
if (!options.files && !options.code) {
cli.showHelp()
}
return standalone(options)
}).then((linted) => {
if (reportNeedlessDisables) {
process.stdout.write(needlessDisablesStringFormatter(linted.needlessDisables))
if (reportNeedlessDisables === "error") {
process.exitCode = 2
}
return
}
if (!linted.output) {
return
}
process.stdout.write(linted.output)
if (linted.errored) {
process.exitCode = 2
}
}).catch(err => {
console.log(err.stack) // eslint-disable-line no-console
const exitCode = typeof err.code === "number" ? err.code : 1
process.exit(exitCode)
})

8
node_modules/stylelint/lib/createPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,8 @@
"use strict"
module.exports = function (ruleName, rule) {
return {
ruleName,
rule,
}
}

41
node_modules/stylelint/lib/createStylelint.js generated vendored Normal file
View File

@@ -0,0 +1,41 @@
/* @flow */
"use strict"
const augmentConfig = require("./augmentConfig")
const _ = require("lodash")
const cosmiconfig = require("cosmiconfig")
const createStylelintResult = require("./createStylelintResult")
const getConfigForFile = require("./getConfigForFile")
const getPostcssResult = require("./getPostcssResult")
const isPathIgnored = require("./isPathIgnored")
const lintSource = require("./lintSource")
// The stylelint "internal API" is passed among functions
// so that methods on a stylelint instance can invoke
// each other while sharing options and caches
module.exports = function (options/*: stylelint$options*/)/*: stylelint$internalApi*/ {
options = options || {}
const stylelint/*: Object*/ = { _options: options }
// Two separate explorers so they can each have their own transform
// function whose results are cached by cosmiconfig
stylelint._fullExplorer = cosmiconfig("stylelint", {
argv: false,
rcExtensions: true,
transform: _.partial(augmentConfig.augmentConfigFull, stylelint),
})
stylelint._extendExplorer = cosmiconfig(null, {
argv: false,
transform: _.partial(augmentConfig.augmentConfigExtended, stylelint),
})
stylelint._specifiedConfigCache = new Map()
stylelint._postcssResultCache = new Map()
stylelint._createStylelintResult = _.partial(createStylelintResult, stylelint)
stylelint._getPostcssResult = _.partial(getPostcssResult, stylelint)
stylelint._lintSource = _.partial(lintSource, stylelint)
stylelint.getConfigForFile = _.partial(getConfigForFile, stylelint)
stylelint.isPathIgnored = _.partial(isPathIgnored, stylelint)
return stylelint
}

64
node_modules/stylelint/lib/createStylelintResult.js generated vendored Normal file
View File

@@ -0,0 +1,64 @@
/* @flow */
"use strict"
const _ = require("lodash")
module.exports = function (
stylelint/*: stylelint$internalApi*/,
postcssResult/*: Object*/,
filePath/*:: ?: string*/
)/*: Promise<stylelint$result>*/ {
const source = !postcssResult.root.source ? undefined : postcssResult.root.source.input.file || postcssResult.root.source.input.id
// Strip out deprecation warnings from the messages
const deprecationMessages = _.remove(postcssResult.messages, { stylelintType: "deprecation" })
const deprecations = deprecationMessages.map(deprecationMessage => {
return {
text: deprecationMessage.text,
reference: deprecationMessage.stylelintReference,
}
})
// Also strip out invalid options
const invalidOptionMessages = _.remove(postcssResult.messages, { stylelintType: "invalidOption" })
const invalidOptionWarnings = invalidOptionMessages.map(invalidOptionMessage => {
return {
text: invalidOptionMessage.text,
}
})
// This defines the stylelint result object that formatters receive
let stylelintResult = {
source,
deprecations,
invalidOptionWarnings,
errored: postcssResult.stylelint.stylelintError,
warnings: postcssResult.messages.map(message => {
return {
line: message.line,
column: message.column,
rule: message.rule,
severity: message.severity,
text: message.text,
}
}),
ignored: postcssResult.stylelint.ignored,
_postcssResult: postcssResult,
}
return stylelint.getConfigForFile(filePath).then((result) => {
const config = result.config
if (config.resultProcessors) {
config.resultProcessors.forEach(resultProcessor => {
// Result processors might just mutate the result object,
// or might return a new one
const returned = resultProcessor(stylelintResult, source)
if (returned) {
stylelintResult = returned
}
})
}
return stylelintResult
})
}

7
node_modules/stylelint/lib/formatters/index.js generated vendored Normal file
View File

@@ -0,0 +1,7 @@
"use strict"
module.exports = {
json: require("./jsonFormatter"),
string: require("./stringFormatter"),
verbose: require("./verboseFormatter"),
}

11
node_modules/stylelint/lib/formatters/jsonFormatter.js generated vendored Normal file
View File

@@ -0,0 +1,11 @@
"use strict"
const _ = require("lodash")
// Omit any properties starting with `_`, which are fake-private
module.exports = function (results) {
const cleanedResults = results.map(result => {
return _.omitBy(result, (value, key) => key[0] === "_")
})
return JSON.stringify(cleanedResults)
}

View File

@@ -0,0 +1,30 @@
"use strict"
const chalk = require("chalk")
const path = require("path")
function logFrom(fromValue) {
if (fromValue.charAt(0) === "<") return fromValue
return path.relative(process.cwd(), fromValue).split(path.sep).join("/")
}
module.exports = function (report) {
let output = ""
report.forEach(sourceReport => {
if (!sourceReport.ranges || sourceReport.ranges.length === 0) {
return
}
output += "\n"
output += chalk.underline(logFrom(sourceReport.source)) + "\n"
sourceReport.ranges.forEach(range => {
output += `start: ${range.start}`
if (range.end !== undefined) {
output += `, end: ${range.end}`
}
output += "\n"
})
})
return output
}

View File

@@ -0,0 +1,140 @@
"use strict"
const table = require("table")
const _ = require("lodash")
const chalk = require("chalk")
const path = require("path")
const stringWidth = require("string-width")
const symbols = require("log-symbols")
const utils = require("postcss-reporter/lib/util")
const MARGIN_WIDTHS = 9
const levelColors = {
info: "blue",
warning: "yellow",
error: "red",
}
function deprecationsFormatter(results) {
const allDeprecationWarnings = _.flatMap(results, "deprecations")
const uniqueDeprecationWarnings = _.uniqBy(allDeprecationWarnings, "text")
if (!uniqueDeprecationWarnings || !uniqueDeprecationWarnings.length) {
return ""
}
return uniqueDeprecationWarnings.reduce((output, warning) => {
output += chalk.yellow("Deprecation Warning: ")
output += warning.text
if (warning.reference) {
output += chalk.dim(" See: ")
output += chalk.dim.underline(warning.reference)
}
return output + "\n"
}, "\n")
}
function invalidOptionsFormatter(results) {
const allInvalidOptionWarnings = _.flatMap(results, r => r.invalidOptionWarnings.map(w => w.text))
const uniqueInvalidOptionWarnings = _.uniq(allInvalidOptionWarnings)
return uniqueInvalidOptionWarnings.reduce((output, warning) => {
output += chalk.red("Invalid Option: ")
output += warning
return output + "\n"
}, "\n")
}
function logFrom(fromValue) {
if (fromValue.charAt(0) === "<") return fromValue
return path.relative(process.cwd(), fromValue).split(path.sep).join("/")
}
function getMessageWidth(columnWidths) {
if (!process.stdout.isTTY) {
return columnWidths[3]
}
const availableWidth = process.stdout.columns < 80 ? 80 : process.stdout.columns
const fullWidth = _.sum(_.values(columnWidths))
// If there is no reason to wrap the text, we won't align the last column to the right
if (availableWidth > fullWidth + MARGIN_WIDTHS) {
return columnWidths[3]
}
return availableWidth - (fullWidth - columnWidths[3] + MARGIN_WIDTHS)
}
function formatter(messages, source) {
if (!messages.length) return ""
const orderedMessages = _.sortBy(messages, m => m.line ? 2 : 1, // positionless first
m => m.line, m => m.column)
// Create a list of column widths, needed to calculate
// the size of the message column and if needed wrap it.
const columnWidths = { 0: 1, 1: 1, 2: 1, 3: 1, 4: 1 }
const calculateWidths = function (columns) {
_.forOwn(columns, (value, key) => {
const normalisedValue = value ? value.toString() : value
columnWidths[key] = Math.max(columnWidths[key], stringWidth(normalisedValue))
})
return columns
}
let output = "\n"
if (source) {
output += chalk.underline(logFrom(source)) + "\n"
}
const cleanedMessages = orderedMessages.map(message => {
const location = utils.getLocation(message)
const severity = message.severity
const row = [ location.line || "", location.column || "", symbols[severity] ? chalk[levelColors[severity]](symbols[severity]) : severity, message.text
// Remove all control characters (newline, tab and etc)
.replace(/[\x01-\x1A]+/g, " ") // eslint-disable-line
.replace(/\.$/, "").replace(new RegExp(_.escapeRegExp("(" + message.rule + ")") + "$"), ""), chalk.dim(message.rule || "") ]
calculateWidths(row)
return row
})
output += table.table(cleanedMessages, {
border: table.getBorderCharacters("void"),
columns: {
0: { alignment: "right", width: columnWidths[0], paddingRight: 0 },
1: { alignment: "left", width: columnWidths[1] },
2: { alignment: "center", width: columnWidths[2] },
3: { alignment: "left", width: getMessageWidth(columnWidths), wrapWord: true },
4: { alignment: "left", width: columnWidths[4], paddingRight: 0 },
},
drawHorizontalLine: () => false,
}).split("\n").map(el => el.replace(/(\d+)\s+(\d+)/, (m, p1, p2) => chalk.dim(p1 + ":" + p2))).join("\n")
return output
}
module.exports = function (results) {
let output = invalidOptionsFormatter(results)
output += deprecationsFormatter(results)
output = results.reduce((output, result) => {
output += formatter(result.warnings, result.source)
return output
}, output)
// Ensure consistent padding
output = output.trim()
if (output !== "") {
output = "\n" + output + "\n\n"
}
return output
}

View File

@@ -0,0 +1,49 @@
"use strict"
const _ = require("lodash")
const chalk = require("chalk")
const stringFormatter = require("./stringFormatter")
module.exports = function (results) {
let output = stringFormatter(results)
if (output === "") {
output = "\n"
}
const sourceWord = results.length > 1 ? "sources" : "source"
const ignoredCount = results.filter(result => result.ignored).length
const checkedDisplay = ignoredCount ? `${results.length - ignoredCount} of ${results.length}` : results.length
output += chalk.underline(`${checkedDisplay} ${sourceWord} checked\n`)
results.forEach(result => {
let formatting = "green"
if (result.errored) {
formatting = "red"
} else if (result.warnings.length) {
formatting = "yellow"
} else if (result.ignored) {
formatting = "dim"
}
let sourceText = `${result.source}`
if (result.ignored) {
sourceText += " (ignored)"
}
output += _.get(chalk, formatting)(` ${sourceText}\n`)
})
const warnings = _.flatten(results.map(r => r.warnings))
const warningsBySeverity = _.groupBy(warnings, "severity")
const problemWord = warnings.length === 1 ? "problem" : "problems"
output += chalk.underline(`\n${warnings.length} ${problemWord} found\n`)
_.forOwn(warningsBySeverity, (warningList, severityLevel) => {
const warningsByRule = _.groupBy(warningList, "rule")
output += ` severity level "${severityLevel}": ${warningList.length}\n`
_.forOwn(warningsByRule, (list, rule) => {
output += chalk.dim(` ${rule}: ${list.length}\n`)
})
})
return output + "\n"
}

43
node_modules/stylelint/lib/getConfigForFile.js generated vendored Normal file
View File

@@ -0,0 +1,43 @@
/* @flow */
"use strict"
const augmentConfigFull = require("./augmentConfig").augmentConfigFull
const configurationError = require("./utils/configurationError")
const path = require("path")
module.exports = function (
stylelint/*: stylelint$internalApi*/,
searchPath/*:: ?: string*/
)/*: Promise<?{ config: stylelint$config, filepath: string }>*/ {
searchPath = searchPath || process.cwd()
const optionsConfig = stylelint._options.config
if (optionsConfig !== undefined) {
const cached = stylelint._specifiedConfigCache.get(optionsConfig)
if (cached) return cached
// stylelint._fullExplorer (cosmiconfig) is already configured to
// run augmentConfigFull; but since we're making up the result here,
// we need to manually run the transform
const augmentedResult = augmentConfigFull(stylelint, {
config: optionsConfig,
// Add the extra path part so that we can get the directory without being
// confused
filepath: path.join(process.cwd(), "argument-config"),
})
stylelint._specifiedConfigCache.set(optionsConfig, augmentedResult)
return augmentedResult
}
return stylelint._fullExplorer.load(searchPath, stylelint._options.configFile).then(config => {
// If no config was found, try looking from process.cwd
if (!config) return stylelint._fullExplorer.load(process.cwd())
return config
}).then(config => {
if (!config) {
const ending = searchPath ? ` for ${searchPath}` : ""
throw configurationError(`No configuration provided${ending}`)
}
return config
})
}

97
node_modules/stylelint/lib/getPostcssResult.js generated vendored Normal file
View File

@@ -0,0 +1,97 @@
/* @flow */
"use strict"
const fs = require("fs")
const lessSyntax = require("postcss-less")
const path = require("path")
const postcss = require("postcss")
const scssSyntax = require("postcss-scss")
const sugarssSyntax = require("sugarss")
const postcssProcessor = postcss()
module.exports = function (stylelint/*: stylelint$internalApi*/)/*: Promise<?Object>*/ {
const options/*: {
code?: string,
codeFilename?: string,
filePath?: string,
codeProcessors?: Array<Function>,
syntax?: stylelint$syntaxes,
customSyntax?: string
}*/ = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}
const cached = stylelint._postcssResultCache.get(options.filePath)
if (cached) return Promise.resolve(cached)
let getCode
if (options.code !== undefined) {
getCode = Promise.resolve(options.code)
} else if (options.filePath) {
getCode = readFile(options.filePath)
}
if (!getCode) {
throw new Error("code or filePath required")
}
return getCode.then(code => {
const customSyntax = stylelint._options.customSyntax
let syntax = stylelint._options.syntax
if (customSyntax) {
try {
syntax = require(customSyntax)
} catch (e) {
throw new Error(`Cannot resolve custom syntax module ${customSyntax}`)
}
} else {
const fileExtension = path.extname(options.filePath || "")
if (syntax === "scss" || !syntax && fileExtension === ".scss") {
syntax = scssSyntax
} else if (syntax === "less" || !syntax && fileExtension === ".less") {
syntax = lessSyntax
} else if (syntax === "sugarss" || !syntax && fileExtension === ".sss") {
syntax = sugarssSyntax
} else if (syntax) {
throw new Error("You must use a valid syntax option, either: scss, less or sugarss")
}
}
const postcssOptions = {}
postcssOptions.from = options.filePath
/*
* PostCSS allows for syntaxes that only contain a parser, however,
* it then expects the syntax to be set as the `parser` option rather than `syntax.
*/
if (syntax && !syntax.stringify) {
postcssOptions.parser = syntax
} else {
postcssOptions.syntax = syntax
}
const source = options.code ? options.codeFilename : options.filePath
let preProcessedCode = code
if (options.codeProcessors) {
options.codeProcessors.forEach(codeProcessor => {
preProcessedCode = codeProcessor(preProcessedCode, source)
})
}
return postcssProcessor.process(preProcessedCode, postcssOptions)
}).then(postcssResult => {
stylelint._postcssResultCache.set(options.filePath, postcssResult)
return postcssResult
})
}
function readFile(filePath/*: string*/)/*: Promise<string>*/ {
return new Promise((resolve, reject) => {
fs.readFile(filePath, "utf8", (err, content) => {
if (err) {
return reject(err)
}
resolve(content)
})
})
}

31
node_modules/stylelint/lib/index.js generated vendored Normal file
View File

@@ -0,0 +1,31 @@
"use strict"
const report = require("./utils/report")
const ruleMessages = require("./utils/ruleMessages")
const validateOptions = require("./utils/validateOptions")
const checkAgainstRule = require("./utils/checkAgainstRule")
const createPlugin = require("./createPlugin")
const createRuleTester = require("./testUtils/createRuleTester")
const createStylelint = require("./createStylelint")
const postcssPlugin = require("./postcssPlugin")
const rules = require("./rules")
const formatters = require("./formatters")
const standalone = require("./standalone")
const api = postcssPlugin
api.utils = {
report,
ruleMessages,
validateOptions,
checkAgainstRule,
}
api.lint = standalone
api.rules = rules
api.formatters = formatters
api.createPlugin = createPlugin
api.createRuleTester = createRuleTester
api.createLinter = createStylelint
module.exports = api

43
node_modules/stylelint/lib/isPathIgnored.js generated vendored Normal file
View File

@@ -0,0 +1,43 @@
/* @flow */
"use strict"
const ignore = require("ignore")
const micromatch = require("micromatch")
const path = require("path")
const alwaysIgnoredGlobs = require("./alwaysIgnoredGlobs")
// To find out if a path is ignored, we need to load the config,
// which may have an ignoreFiles property,
// and will have incorporated any .stylelintignore file that was found
// into its ignorePatterns property. We then check the path
// against these.
module.exports = function (
stylelint/*: stylelint$internalApi*/,
filePathArg/*:: ?: string*/
)/*: Promise<boolean>*/ {
const filePath = filePathArg // to please Flow
if (!filePath) {
return Promise.resolve(false)
}
return stylelint.getConfigForFile(filePath).then((result) => {
const config = result.config
const absoluteFilePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath)
const ignoreFiles = alwaysIgnoredGlobs.concat(config.ignoreFiles || [])
if (micromatch(absoluteFilePath, ignoreFiles).length) {
return true
}
const ignorePatternsFilter = ignore().add(config.ignorePatterns).createFilter()
const filepathRelativeToCwd = path.relative(process.cwd(), filePath)
if (ignorePatternsFilter && !ignorePatternsFilter(filepathRelativeToCwd)) {
return true
}
return false
})
}

140
node_modules/stylelint/lib/lintSource.js generated vendored Normal file
View File

@@ -0,0 +1,140 @@
/* @flow */
"use strict"
const _ = require("lodash")
const assignDisabledRanges = require("./assignDisabledRanges")
const configurationError = require("./utils/configurationError")
const path = require("path")
const ruleDefinitions = require("./rules")
// Run stylelint on a PostCSS Result, either one that is provided
// or one that we create
module.exports = function (
stylelint/*: stylelint$internalApi*/,
options/*: {
code?: string,
codeFilename?: string, // Must be an absolute file path
filePath?: string, // Must be an absolute file path
existingPostcssResult?: Object,
}*/
)/*: Promise<Object>*/ {
options = options || {}
if (!options.filePath && options.code === undefined && !options.existingPostcssResult) {
return Promise.reject(new Error("You must provide filePath, code, or existingPostcssResult"))
}
const isCodeNotFile = options.code !== undefined
const inputFilePath = isCodeNotFile ? options.codeFilename : options.filePath
if (inputFilePath !== undefined && !path.isAbsolute(inputFilePath)) {
if (isCodeNotFile) {
return Promise.reject(new Error("codeFilename must be an absolute path"))
} else {
return Promise.reject(new Error("filePath must be an absolute path"))
}
}
const getIsIgnored = stylelint.isPathIgnored(inputFilePath).catch(err => {
if (isCodeNotFile && err.code === "ENOENT") return false
throw err
})
return getIsIgnored.then(isIgnored => {
if (isIgnored) {
const postcssResult = options.existingPostcssResult || createEmptyPostcssResult(inputFilePath)
postcssResult.stylelint = postcssResult.stylelint || {}
postcssResult.stylelint.ignored = true
postcssResult.standaloneIgnored = true // TODO: remove need for this
return postcssResult
}
const configSearchPath = stylelint._options.configFile || inputFilePath
const getConfig = stylelint.getConfigForFile(configSearchPath).catch(err => {
if (isCodeNotFile && err.code === "ENOENT") return stylelint.getConfigForFile(process.cwd())
throw err
})
return getConfig.then((result) => {
const config = result.config
const existingPostcssResult = options.existingPostcssResult
if (existingPostcssResult) {
return lintPostcssResult(stylelint, existingPostcssResult, config).then(() => existingPostcssResult)
}
return stylelint._getPostcssResult({
code: options.code,
codeFilename: options.codeFilename,
filePath: inputFilePath,
codeProcessors: config.codeProcessors,
}).then(postcssResult => {
return lintPostcssResult(stylelint, postcssResult, config).then(() => postcssResult)
})
})
})
}
function lintPostcssResult(
stylelint/*: stylelint$internalApi*/,
postcssResult/*: Object*/,
config/*: stylelint$config*/
)/*: Promise<>*/ {
postcssResult.stylelint = postcssResult.stylelint || {}
postcssResult.stylelint.ruleSeverities = {}
postcssResult.stylelint.customMessages = {}
postcssResult.stylelint.quiet = config.quiet
const postcssRoot = postcssResult.root
assignDisabledRanges(postcssRoot, postcssResult)
if (stylelint._options.reportNeedlessDisables || stylelint._options.ignoreDisables) {
postcssResult.stylelint.ignoreDisables = true
}
// Promises for the rules. Although the rule code runs synchronously now,
// the use of Promises makes it compatible with the possibility of async
// rules down the line.
const performRules = []
const rules = config.rules ? Object.keys(config.rules) : []
rules.forEach(ruleName => {
const ruleFunction = ruleDefinitions[ruleName] || _.get(config, [ "pluginFunctions", ruleName ])
if (ruleFunction === undefined) {
throw configurationError(`Undefined rule ${ruleName}`)
}
const ruleSettings = _.get(config, [ "rules", ruleName ])
if (ruleSettings === null || ruleSettings[0] === null) {
return
}
const primaryOption = ruleSettings[0]
const secondaryOptions = ruleSettings[1]
// Log the rule's severity in the PostCSS result
const defaultSeverity = config.defaultSeverity || "error"
postcssResult.stylelint.ruleSeverities[ruleName] = _.get(secondaryOptions, "severity", defaultSeverity)
postcssResult.stylelint.customMessages[ruleName] = _.get(secondaryOptions, "message")
const performRule = Promise.resolve().then(() => {
ruleFunction(primaryOption, secondaryOptions)(postcssRoot, postcssResult)
})
performRules.push(performRule)
})
return Promise.all(performRules)
}
function createEmptyPostcssResult(filePath/*:: ?: string*/)/*: Object*/ {
return {
root: {
source: {
input: { file: filePath },
},
},
messages: [],
stylelint: { stylelintError: null },
}
}

89
node_modules/stylelint/lib/needlessDisables.js generated vendored Normal file
View File

@@ -0,0 +1,89 @@
"use strict"
/* flow */
const _ = require("lodash")
module.exports = function (results/*: Array<stylelint$result>*/)/*: stylelint$needlessDisablesReport*/ {
const report = []
results.forEach(result => {
// File with `CssSyntaxError` have not `_postcssResult`
if (!result._postcssResult) {
return
}
const unused = { source: result.source, ranges: [] }
const rangeData = _.cloneDeep(result._postcssResult.stylelint.disabledRanges)
if (!rangeData) {
return
}
result.warnings.forEach(warning => {
const rule = warning.rule
const ruleRanges = rangeData[rule]
if (ruleRanges) {
// Back to front so we get the *last* range that applies to the warning
for (const range of ruleRanges.reverse()) {
if (isWarningInRange(warning, range)) {
range.used = true
return
}
}
}
for (const range of rangeData.all.reverse()) {
if (isWarningInRange(warning, range)) {
range.used = true
return
}
}
})
Object.keys(rangeData).forEach(rule => {
rangeData[rule].forEach(range => {
// Is an equivalent range already marked as unused?
const alreadyMarkedUnused = unused.ranges.find(unusedRange => {
return unusedRange.start === range.start && unusedRange.end === range.end
})
// If this range is unused and no equivalent is marked,
// mark this range as unused
if (!range.used && !alreadyMarkedUnused) {
unused.ranges.push(range)
}
// If this range is used but an equivalent has been marked as unused,
// remove that equivalent. This can happen because of the duplication
// of ranges in rule-specific range sets and the "all" range set
if (range.used && alreadyMarkedUnused) {
_.remove(unused.ranges, alreadyMarkedUnused)
}
})
})
unused.ranges = _.sortBy(unused.ranges, [ "start", "end" ])
report.push(unused)
})
return report
}
function isWarningInRange(
warning/*: {
rule: string,
line: number
}*/,
range/*: {
rules?: Array<string>,
start: number,
end?: number,
}*/
)/*: boolean*/ {
const rule = warning.rule,
line = warning.line
return range.start <= line && (range.end >= line || range.end === undefined) && (!range.rules || range.rules.indexOf(rule) !== -1)
}

69
node_modules/stylelint/lib/normalizeRuleSettings.js generated vendored Normal file
View File

@@ -0,0 +1,69 @@
/* @flow */
"use strict"
const _ = require("lodash")
const rules = require("./rules")
// Rule settings can take a number of forms, e.g.
// a. "rule-name": null
// b. "rule-name": [null, ...]
// c. "rule-name": primaryOption
// d. "rule-name": [primaryOption]
// e. "rule-name": [primaryOption, secondaryOption]
// Where primaryOption can be anything: primitive, Object, or Array.
//
// This function normalizes all the possibilities into the
// standard form: [primaryOption, secondaryOption]
// Except in the cases with null, a & b, in which case
// null is returned
module.exports = function (
rawSettings/*: stylelint$configRuleSettings*/,
ruleName/*: string*/,
// If primaryOptionArray is not provided, we try to get it from the
// rules themselves, which will not work for plugins
primaryOptionArray/*:: ?: boolean*/
)/*: [any, Object] | Array<any | [any, Object]> | null*/ {
if (rawSettings === null) {
return null
}
if (!Array.isArray(rawSettings)) {
return [rawSettings]
}
// Everything below is an array ...
if (rawSettings[0] === null) {
return null
}
// This cursed rule needs a special case
if (ruleName === "declaration-block-properties-order") {
if (rawSettings[0] === "alphabetical") {
return rawSettings
}
if (typeof rawSettings[0] === "string") {
return [rawSettings]
}
}
if (primaryOptionArray === undefined) {
const rule = rules[ruleName]
primaryOptionArray = _.get(rule, "primaryOptionArray")
}
if (!primaryOptionArray) {
return rawSettings
}
// Everything below is a rule that CAN have an array for a primary option ...
// (they might also have something else, e.g. rule-properties-order can
// have the string "alphabetical")
if (rawSettings.length === 1 && Array.isArray(rawSettings[0])) {
return rawSettings
}
if (rawSettings.length === 2 && !_.isPlainObject(rawSettings[0]) && _.isPlainObject(rawSettings[1])) {
return rawSettings
}
return [rawSettings]
}

27
node_modules/stylelint/lib/postcssPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,27 @@
/* @flow */
"use strict"
const _ = require("lodash")
const createStylelint = require("./createStylelint")
const postcss = require("postcss")
const path = require("path")
module.exports = postcss.plugin("stylelint", function (options) {
options = options || {}
const tailoredOptions/*: Object*/ = options.rules
? { config: options }
: options
const stylelint = createStylelint(tailoredOptions)
return (root, result) => {
let filePath = options.from || _.get(root, "source.input.file")
if (filePath !== undefined && !path.isAbsolute(filePath)) {
filePath = path.join(process.cwd(), filePath)
}
return stylelint._lintSource({
filePath,
existingPostcssResult: result,
})
}
})

618
node_modules/stylelint/lib/reference/keywordSets.js generated vendored Normal file
View File

@@ -0,0 +1,618 @@
"use strict"
const _ = require("lodash")
const keywordSets = {}
keywordSets.nonLengthUnits = new Set([
// Relative length units
"%",
// Time length units
"s",
"ms",
// Angle
"deg",
"grad",
"turn",
"rad",
// Frequency
"Hz",
"kHz",
// Resolution
"dpi",
"dpcm",
"dppx",
])
keywordSets.lengthUnits = new Set([
// Relative length units
"em",
"ex",
"ch",
"rem",
// Viewport-percentage lengths
"vh",
"vw",
"vmin",
"vmax",
"vm",
// Absolute length units
"px",
"mm",
"cm",
"in",
"pt",
"pc",
"q",
// Flexible length units
"fr",
])
keywordSets.units = uniteSets(keywordSets.nonLengthUnits, keywordSets.lengthUnits)
keywordSets.colorFunctionNames = new Set([
"rgb",
"rgba",
"hsl",
"hsla",
"hwb",
"gray",
])
keywordSets.camelCaseFunctionNames = new Set([
"translateX",
"translateY",
"translateZ",
"scaleX",
"scaleY",
"scaleZ",
"rotateX",
"rotateY",
"rotateZ",
"skewX",
"skewY",
])
keywordSets.basicKeywords = new Set([
"initial",
"inherit",
"unset",
])
keywordSets.fontFamilyKeywords = uniteSets(keywordSets.basicKeywords, [
"serif",
"sans-serif",
"cursive",
"fantasy",
"monospace",
])
keywordSets.fontWeightRelativeKeywords = new Set([
"bolder",
"lighter",
])
keywordSets.fontWeightAbsoluteKeywords = new Set(["bold"])
keywordSets.fontWeightNumericKeywords = new Set([
"100",
"200",
"300",
"400",
"500",
"600",
"700",
"800",
"900",
])
keywordSets.fontWeightKeywords = uniteSets(
keywordSets.basicKeywords,
keywordSets.fontWeightRelativeKeywords,
keywordSets.fontWeightAbsoluteKeywords,
keywordSets.fontWeightNumericKeywords
)
keywordSets.animationNameKeywords = uniteSets(keywordSets.basicKeywords,
["none"]
)
keywordSets.animationTimingFunctionKeywords = uniteSets(keywordSets.basicKeywords, [
"linear",
"ease",
"ease-in",
"ease-in-out",
"ease-out",
"step-start",
"step-end",
"steps",
"cubic-bezier",
])
keywordSets.animationIterationCountKeywords = new Set(["infinite"])
keywordSets.animationDirectionKeywords = uniteSets(keywordSets.basicKeywords, [
"normal",
"reverse",
"alternate",
"alternate-reverse",
])
keywordSets.animationFillModeKeywords = new Set([
"none",
"forwards",
"backwards",
"both",
])
keywordSets.animationPlayStateKeywords = uniteSets(keywordSets.basicKeywords, [
"running",
"paused",
])
// cf. https://developer.mozilla.org/en-US/docs/Web/CSS/animation
keywordSets.animationShorthandKeywords = uniteSets(
keywordSets.basicKeywords,
keywordSets.animationNameKeywords,
keywordSets.animationTimingFunctionKeywords,
keywordSets.animationIterationCountKeywords,
keywordSets.animationDirectionKeywords,
keywordSets.animationFillModeKeywords,
keywordSets.animationPlayStateKeywords
)
// These are the ones that can have single-colon notation
keywordSets.levelOneAndTwoPseudoElements = new Set([
"before",
"after",
"first-line",
"first-letter",
])
// These are the ones that require double-colon notation
keywordSets.levelThreePseudoElements = new Set([
"before",
"after",
"first-line",
"first-letter",
"selection",
"spelling-error",
"grammar-error",
"backdrop",
"marker",
"placeholder",
"shadow",
"slotted",
"content",
])
keywordSets.pseudoElements = uniteSets(
keywordSets.levelOneAndTwoPseudoElements,
keywordSets.levelThreePseudoElements
)
keywordSets.aNPlusBNotationPseudoClasses = new Set([
"nth-child",
"nth-column",
"nth-last-child",
"nth-last-column",
"nth-last-of-type",
"nth-of-type",
])
keywordSets.linguisticPseudoClasses = new Set([
"dir",
"lang",
])
keywordSets.otherPseudoClasses = new Set([
"active",
"any-link",
"blank",
"checked",
"contains",
"current",
"default",
"disabled",
"drop",
"empty",
"enabled",
"first-child",
"first-of-type",
"focus",
"focus-within",
"fullscreen",
"future",
"has",
"host",
"host-context",
"hover",
"indeterminate",
"in-range",
"invalid",
"last-child",
"last-of-type",
"link",
"matches",
"not",
"only-child",
"only-of-type",
"optional",
"out-of-range",
"past",
"placeholder-shown",
"read-only",
"read-write",
"required",
"root",
"scope",
"target",
"user-error",
"user-invalid",
"val",
"valid",
"visited",
])
keywordSets.webkitProprietaryPseudoElements = new Set([
"scrollbar",
"scrollbar-button",
"scrollbar-track",
"scrollbar-track-piece",
"scrollbar-thumb",
"scrollbar-corner",
"resize",
])
keywordSets.webkitProprietaryPseudoClasses = new Set([
"horizontal",
"vertical",
"decrement",
"increment",
"start",
"end",
"double-button",
"single-button",
"no-button",
"corner-present",
"window-inactive",
])
keywordSets.pseudoClasses = uniteSets(
keywordSets.aNPlusBNotationPseudoClasses,
keywordSets.linguisticPseudoClasses,
keywordSets.otherPseudoClasses
)
keywordSets.shorthandTimeProperties = new Set([
"transition",
"animation",
])
keywordSets.longhandTimeProperties = new Set([
"transition-duration",
"transition-delay",
"animation-duration",
"animation-delay",
])
keywordSets.timeProperties = uniteSets(
keywordSets.shorthandTimeProperties,
keywordSets.longhandTimeProperties
)
keywordSets.camelCaseKeywords = new Set([
"optimizeSpeed",
"optimizeQuality",
"optimizeLegibility",
"geometricPrecision",
"currentColor",
"crispEdges",
"visiblePainted",
"visibleFill",
"visibleStroke",
"sRGB",
"linearRGB",
])
// https://developer.mozilla.org/docs/Web/CSS/counter-increment
keywordSets.counterIncrementKeywords = uniteSets(keywordSets.basicKeywords, ["none"])
keywordSets.gridRowKeywords = uniteSets(keywordSets.basicKeywords, [
"auto",
"span",
])
keywordSets.gridColumnKeywords = uniteSets(keywordSets.basicKeywords, [
"auto",
"span",
])
keywordSets.gridAreaKeywords = uniteSets(keywordSets.basicKeywords, [
"auto",
"span",
])
// https://developer.mozilla.org/ru/docs/Web/CSS/list-style-type
keywordSets.listStyleTypeKeywords = uniteSets(keywordSets.basicKeywords, [
"none",
"disc",
"circle",
"square",
"decimal",
"cjk-decimal",
"decimal-leading-zero",
"lower-roman",
"upper-roman",
"lower-greek",
"lower-alpha",
"lower-latin",
"upper-alpha",
"upper-latin",
"arabic-indic",
"armenian",
"bengali",
"cambodian",
"cjk-earthly-branch",
"cjk-ideographic",
"devanagari",
"ethiopic-numeric",
"georgian",
"gujarati",
"gurmukhi",
"hebrew",
"hiragana",
"hiragana-iroha",
"japanese-formal",
"japanese-informal",
"kannada",
"katakana",
"katakana-iroha",
"khmer",
"korean-hangul-formal",
"korean-hanja-formal",
"korean-hanja-informal",
"lao",
"lower-armenian",
"malayalam",
"mongolian",
"myanmar",
"oriya",
"persian",
"simp-chinese-formal",
"simp-chinese-informal",
"tamil",
"telugu",
"thai",
"tibetan",
"trad-chinese-formal",
"trad-chinese-informal",
"upper-armenian",
"disclosure-open",
"disclosure-closed",
// Non-standard extensions (without prefixe)
"ethiopic-halehame",
"ethiopic-halehame-am",
"ethiopic-halehame-ti-er",
"ethiopic-halehame-ti-et",
"hangul",
"hangul-consonant",
"urdu",
])
keywordSets.listStylePositionKeywords = uniteSets(keywordSets.basicKeywords, [
"inside",
"outside",
])
keywordSets.listStyleImageKeywords = uniteSets(keywordSets.basicKeywords, ["none"])
keywordSets.listStyleShorthandKeywords = uniteSets(
keywordSets.basicKeywords,
keywordSets.listStyleTypeKeywords,
keywordSets.listStylePositionKeywords,
keywordSets.listStyleImageKeywords
)
keywordSets.fontStyleKeywords = uniteSets(keywordSets.basicKeywords, [
"normal",
"italic",
"oblique",
])
keywordSets.fontVariantKeywords = uniteSets(keywordSets.basicKeywords, [
"normal",
"none",
"historical-forms",
"none",
"common-ligatures",
"no-common-ligatures",
"discretionary-ligatures",
"no-discretionary-ligatures",
"historical-ligatures",
"no-historical-ligatures",
"contextual",
"no-contextual",
"small-caps",
"small-caps",
"all-small-caps",
"petite-caps",
"all-petite-caps",
"unicase",
"titling-caps",
"lining-nums",
"oldstyle-nums",
"proportional-nums",
"tabular-nums",
"diagonal-fractions",
"stacked-fractions",
"ordinal",
"slashed-zero",
"jis78",
"jis83",
"jis90",
"jis04",
"simplified",
"traditional",
"full-width",
"proportional-width",
"ruby",
])
keywordSets.fontStretchKeywords = uniteSets(keywordSets.basicKeywords, [
"semi-condensed",
"condensed",
"extra-condensed",
"ultra-condensed",
"semi-expanded",
"expanded",
"extra-expanded",
"ultra-expanded",
])
keywordSets.fontSizeKeywords = uniteSets(keywordSets.basicKeywords, [
"xx-small",
"x-small",
"small",
"medium",
"large",
"x-large",
"xx-large",
"larger",
"smaller",
])
keywordSets.lineHeightKeywords = uniteSets(keywordSets.basicKeywords, ["normal"])
keywordSets.fontShorthandKeywords = uniteSets(
keywordSets.basicKeywords,
keywordSets.fontStyleKeywords,
keywordSets.fontVariantKeywords,
keywordSets.fontWeightKeywords,
keywordSets.fontStretchKeywords,
keywordSets.fontSizeKeywords,
keywordSets.lineHeightKeywords, keywordSets.fontFamilyKeywords
)
keywordSets.keyframeSelectorKeywords = new Set([
"from",
"to",
])
// https://developer.mozilla.org/en/docs/Web/CSS/At-rule
keywordSets.atRules = new Set([
"apply",
"annotation",
"character-variant",
"charset",
"counter-style",
"custom-media",
"custom-selector",
"document",
"font-face",
"font-feature-values",
"import",
"keyframes",
"media",
"namespace",
"nest",
"ornaments",
"page",
"styleset",
"stylistic",
"supports",
"swash",
"viewport",
])
// https://drafts.csswg.org/mediaqueries/#descdef-media-update
keywordSets.deprecatedMediaFeatureNames = new Set([
"device-aspect-ratio",
"device-height",
"device-width",
"max-device-aspect-ratio",
"max-device-height",
"max-device-width",
"min-device-aspect-ratio",
"min-device-height",
"min-device-width",
])
// https://drafts.csswg.org/mediaqueries/#descdef-media-update
keywordSets.mediaFeatureNames = uniteSets(keywordSets.deprecatedMediaFeatureNames, [
"any-hover",
"any-pointer",
"aspect-ratio",
"color",
"color-gamut",
"color-index",
"grid",
"height",
"hover",
"max-aspect-ratio",
"max-color",
"max-color-index",
"max-height",
"max-monochrome",
"max-resolution",
"max-width",
"min-aspect-ratio",
"min-color",
"min-color-index",
"min-height",
"min-monochrome",
"min-resolution",
"min-width",
"monochrome",
"orientation",
"overflow-block",
"overflow-inline",
"pointer",
"resolution",
"scan",
"scripting",
"update",
"width",
])
// https://www.w3.org/TR/CSS22/ui.html#system-colors
keywordSets.systemColors = new Set([
"activeborder",
"activecaption",
"appworkspace",
"background",
"buttonface",
"buttonhighlight",
"buttonshadow",
"buttontext",
"captiontext",
"graytext",
"highlight",
"highlighttext",
"inactiveborder",
"inactivecaption",
"inactivecaptiontext",
"infobackground",
"infotext",
"menu",
"menutext",
"scrollbar",
"threeddarkshadow",
"threedface",
"threedhighlight",
"threedlightshadow",
"threedshadow",
"window",
"windowframe",
"windowtext",
])
function uniteSets() {
const sets = Array.from(arguments)
return new Set(sets.reduce((result, set) => {
return result.concat(_.toArray(set))
}, []))
}
module.exports = keywordSets

2858
node_modules/stylelint/lib/reference/namedColorData.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

18
node_modules/stylelint/lib/reference/propertySets.js generated vendored Normal file
View File

@@ -0,0 +1,18 @@
"use strict"
const propertySets = {}
propertySets.acceptCustomIdents = new Set([
"animation",
"animation-name",
"font",
"font-family",
"counter-increment",
"grid-row",
"grid-column",
"grid-area",
"list-style",
"list-style-type",
])
module.exports = propertySets

View File

@@ -0,0 +1,20 @@
"use strict"
const punctuationSets = {}
punctuationSets.mediaFeaturePunctuation = new Set([
":",
"=",
">",
">=",
"<",
"<=",
])
punctuationSets.nonSpaceCombinators = new Set([
">",
"+",
"~",
])
module.exports = punctuationSets

120
node_modules/stylelint/lib/reference/shorthandData.js generated vendored Normal file
View File

@@ -0,0 +1,120 @@
"use strict"
module.exports = {
"margin": [
"margin-top",
"margin-bottom",
"margin-left",
"margin-right",
],
"padding": [
"padding-top",
"padding-bottom",
"padding-left",
"padding-right",
],
"background": [
"background-image",
"background-size",
"background-position",
"background-repeat",
"background-origin",
"background-clip",
"background-attachment",
"background-color",
],
"font": [
"font-style",
"font-variant",
"font-weight",
"font-stretch",
"font-size",
"font-family",
"line-height",
],
"border": [
"border-top-width",
"border-bottom-width",
"border-left-width",
"border-right-width",
"border-top-style",
"border-bottom-style",
"border-left-style",
"border-right-style",
"border-top-color",
"border-bottom-color",
"border-left-color",
"border-right-color",
],
"border-top": [
"border-top-width",
"border-top-style",
"border-top-color",
],
"border-bottom": [
"border-bottom-width",
"border-bottom-style",
"border-bottom-color",
],
"border-left": [
"border-left-width",
"border-left-style",
"border-left-color",
],
"border-right": [
"border-right-width",
"border-right-style",
"border-right-color",
],
"border-width": [
"border-top-width",
"border-bottom-width",
"border-left-width",
"border-right-width",
],
"border-style": [
"border-top-style",
"border-bottom-style",
"border-left-style",
"border-right-style",
],
"border-color": [
"border-top-color",
"border-bottom-color",
"border-left-color",
"border-right-color",
],
"list-style": [
"list-style-type",
"list-style-position",
"list-style-image",
],
"border-radius": [
"border-top-right-radius",
"border-top-left-radius",
"border-bottom-right-radius",
"border-bottom-left-radius",
],
"transition": [
"transition-delay",
"transition-duration",
"transition-property",
"transition-timing-function",
],
"-webkit-transition": [
"-webkit-transition-delay",
"-webkit-transition-duration",
"-webkit-transition-property",
"-webkit-transition-timing-function",
],
"-moz-transition": [
"-moz-transition-delay",
"-moz-transition-duration",
"-moz-transition-property",
"-moz-transition-timing-function",
],
"-o-transition": [ "-o-transition-delay",
"-o-transition-duration",
"-o-transition-property",
"-o-transition-timing-function" ],
}

View File

@@ -0,0 +1,45 @@
# at-rule-blacklist
Specify a blacklist of disallowed at-rules.
```css
@keyframes name {}
/** ↑
* At-rules like this */
```
## Options
`array|string`: `["array", "of", "unprefixed", "at-rules"]|"at-rule"`
Given:
```js
["extend", "keyframes"]
```
The following patterns are considered warnings:
```css
a { @extend placeholder; }
```
```css
@keyframes name {
from { top: 10px; }
to { top: 20px; }
}
```
```css
@-moz-keyframes name {
from { top: 10px; }
to { top: 20px; }
}
```
The following patterns are *not* considered warnings:
```css
@import "path/to/file.css";
```

View File

@@ -0,0 +1,48 @@
"use strict"
const _ = require("lodash")
const postcss = require("postcss")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "at-rule-blacklist"
const messages = ruleMessages(ruleName, {
rejected: name => `Unexpected at-rule "${name}"`,
})
const rule = function (blacklistInput) {
// To allow for just a string as a parameter (not only arrays of strings)
const blacklist = [].concat(blacklistInput)
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: blacklist,
possible: [_.isString],
})
if (!validOptions) {
return
}
root.walkAtRules(atRule => {
const name = atRule.name
if (blacklist.indexOf(postcss.vendor.unprefixed(name).toLowerCase()) === -1) {
return
}
report({
message: messages.rejected(name),
node: atRule,
result,
ruleName,
})
})
}
}
rule.primaryOptionArray = true
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,353 @@
# at-rule-empty-line-before
Require or disallow an empty line before at-rules.
```css
a {}
/* ← */
@media {} /* ↑ */
/** ↑
* This line */
```
If the at-rule is the very first node in a stylesheet then it is ignored.
## Options
`string`: `"always"|"never"`
### `"always"`
There *must always* be an empty line before at-rules.
The following patterns are considered warnings:
```css
a {} @media {}
```
```css
a {}
@media {}
```
The following patterns are *not* considered warnings:
```css
a {}
@media {}
```
### `"never"`
There *must never* be an empty line before at-rules.
The following patterns are considered warnings:
```css
a {}
@media {}
```
The following patterns are *not* considered warnings:
```css
a {} @media {}
```
```css
a {}
@media {}
```
## Optional secondary options
### `except: ["after-same-name", "inside-block", "blockless-after-same-name-blockless", "blockless-after-blockless", "first-nested"]`
#### `"after-same-name"`
Reverse the primary option for at-rules that follow another at-rule with the same name.
This means that you can group your at-rules by name.
For example, with `"always"`:
The following patterns are *not* considered warnings:
```css
@charset "UTF-8";
@import url(x.css);
@import url(y.css);
@media (min-width: 100px) {}
@media (min-width: 200px) {}
```
```css
a {
@extends .foo;
@extends .bar;
@include x;
@include y {}
}
```
#### `"inside-block"`
***Note: This option was previously called `all-nested`.***
Reverse the primary option for at-rules that are nested.
For example, with `"always"`:
The following patterns are considered warnings:
```css
a {
@extend foo;
color: pink;
}
b {
color: pink;
@extend foo;
}
```
The following patterns are *not* considered warnings:
```css
a {
@extend foo;
color: pink;
}
b {
color: pink;
@extend foo;
}
```
#### `"blockless-after-same-name-blockless"`
Reverse the primary option for blockless at-rules that follow another blockless at-rule with the same name.
This means that you can group your blockless at-rules by name.
For example, with `"always"`:
The following patterns are *not* considered warnings:
```css
@charset "UTF-8";
@import url(x.css);
@import url(y.css);
```
```css
a {
@extends .foo;
@extends .bar;
@include loop;
@include doo;
}
```
#### `"blockless-after-blockless"`
***Note: This option was previously called `blockless-group`.***
Reverse the primary option for at-rules within a blockless group.
For example, with `"always"`:
The following patterns are considered warnings:
```css
@import url(x.css);
@import url(y.css);
@media print {}
```
The following patterns are *not* considered warnings:
```css
@import url(x.css);
@import url(y.css);
@media print {}
```
#### `"first-nested"`
Reverse the primary option for at-rules that are nested and the first child of their parent node.
For example, with `"always"`:
The following patterns are considered warnings:
```css
a {
@extend foo;
color: pink;
}
b {
color: pink;
@extend foo;
}
```
The following patterns are *not* considered warnings:
```css
a {
@extend foo;
color: pink;
}
b {
color: pink;
@extend foo;
}
```
### `ignore: ["after-comment", "inside-block", "blockless-after-same-name-blockless", "blockless-after-blockless"]`
#### `"after-comment"`
Ignore at-rules that come after a comment.
The following patterns are *not* considered warnings:
```css
/* comment */
@media {}
```
```css
/* comment */
@media {}
```
#### `"inside-block"`
***Note: This option was previously called `all-nested`.***
Ignore at-rules that are inside a declaration block.
For example, with `"always"`:
The following patterns are *not* considered warnings:
```css
a {
@extend foo;
color: pink;
}
a {
@extend foo;
color: pink;
}
b {
color: pink;
@extend foo;
}
b {
color: pink;
@extend foo;
}
```
#### `"blockless-after-same-name-blockless"`
Ignore blockless at-rules that follow another blockless at-rule with the same name.
This means that you can group your blockless at-rules by name.
For example, with `"always"`:
The following patterns are *not* considered warnings:
```css
@charset "UTF-8";
@import url(x.css);
@import url(y.css);
```
```css
a {
@extends .foo;
@extends .bar;
@include loop;
@include doo;
}
```
#### `"blockless-after-blockless"`
***Note: This option was previously called `blockless-group`.***
Ignore blockless at-rules that follow another blockless at-rule.
For example, with `"always"`:
The following patterns are *not* considered warnings:
```css
@import url(x.css);
@import url(y.css);
@media print {}
```
```css
@import url(x.css);
@import url(y.css);
@media print {}
```
### `ignoreAtRules: ["array", "of", "at-rules"]`
Ignore specified at-rules.
For example, with `"always"`.
Given:
```js
["import"]
```
The following patterns are *not* considered warnings:
```css
@charset "UTF-8";
@import {}
```

View File

@@ -0,0 +1,203 @@
"use strict"
const _ = require("lodash")
const hasBlock = require("../../utils/hasBlock")
const hasEmptyLine = require("../../utils/hasEmptyLine")
const optionsMatches = require("../../utils/optionsMatches")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "at-rule-empty-line-before"
const messages = ruleMessages(ruleName, {
expected: "Expected empty line before at-rule",
rejected: "Unexpected empty line before at-rule",
})
const rule = function (expectation, options) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"always",
"never",
],
}, {
actual: options,
possible: {
except: [
"after-same-name",
"all-nested",
"inside-block",
"blockless-after-same-name-blockless",
"blockless-group",
"blockless-after-blockless",
"first-nested",
],
ignore: [
"after-comment",
"all-nested",
"inside-block",
"blockless-after-same-name-blockless",
"blockless-group",
"blockless-after-blockless",
],
ignoreAtRules: [_.isString],
},
optional: true,
})
if (!validOptions) {
return
}
if (
optionsMatches(options, "ignore", "all-nested")
|| optionsMatches(options, "except", "all-nested")
) {
result.warn((
"'at-rule-empty-line-before\'s' \"all-nested\" option has been deprecated and in 8.0 will be removed. " +
"Instead use the \"inside-block\" option."
), {
stylelintType: "deprecation",
stylelintReference: "https://stylelint.io/user-guide/rules/at-rule-empty-line-before/",
})
}
if (
optionsMatches(options, "ignore", "blockless-group")
|| optionsMatches(options, "except", "blockless-group")
) {
result.warn((
"'at-rule-empty-line-before\'s' \"blockless-group\" option has been deprecated and in 8.0 will be removed. " +
"Instead use the \"blockless-after-blockless\" option."
), {
stylelintType: "deprecation",
stylelintReference: "https://stylelint.io/user-guide/rules/at-rule-empty-line-before/",
})
}
root.walkAtRules(atRule => {
// Ignore the first node
if (atRule === root.first) {
return
}
// Return early if at-rule is to be ignored
if (optionsMatches(options, "ignoreAtRules", atRule.name)) {
return
}
// Optionally ignore the expectation if the node is blockless
if (
optionsMatches(options, "ignore", "blockless-group")
&& !hasBlock(atRule)
|| optionsMatches(options, "ignore", "blockless-after-blockless")
&& !hasBlock(atRule)
) {
return
}
const isNested = atRule.parent !== root
const previousNode = atRule.prev()
// Optionally ignore the expection if the node is blockless
// and following another blockless at-rule with the same name
if (
optionsMatches(options, "ignore", "blockless-after-same-name-blockless")
&& isBlocklessAfterSameNameBlockless()
) {
return
}
// Optionally ignore the expectation if the node is inside a block
if (
optionsMatches(options, "ignore", "all-nested")
&& isNested
|| optionsMatches(options, "ignore", "inside-block")
&& isNested
) {
return
}
// Optionally ignore the expectation if a comment precedes this node
if (
optionsMatches(options, "ignore", "after-comment")
&& isAfterComment()
) {
return
}
const hasEmptyLineBefore = hasEmptyLine(atRule.raws.before)
let expectEmptyLineBefore = expectation === "always"
? true
: false
// Optionally reverse the expectation if any exceptions apply
if (
optionsMatches(options, "except", "after-same-name")
&& isAfterSameName()
|| optionsMatches(options, "except", "all-nested")
&& isNested
|| optionsMatches(options, "except", "inside-block")
&& isNested
|| optionsMatches(options, "except", "first-nested")
&& isFirstNested()
|| optionsMatches(options, "except", "blockless-group")
&& isBlocklessAfterBlockless()
|| optionsMatches(options, "except", "blockless-after-blockless")
&& isBlocklessAfterBlockless()
|| optionsMatches(options, "except", "blockless-after-same-name-blockless")
&& isBlocklessAfterSameNameBlockless()
) {
expectEmptyLineBefore = !expectEmptyLineBefore
}
// Return if the expectation is met
if (expectEmptyLineBefore === hasEmptyLineBefore) {
return
}
const message = expectEmptyLineBefore
? messages.expected
: messages.rejected
report({ message, node: atRule, result, ruleName })
function isAfterComment() {
return previousNode
&& previousNode.type === "comment"
}
function isBlocklessAfterBlockless() {
return previousNode
&& previousNode.type === "atrule"
&& !hasBlock(previousNode)
&& !hasBlock(atRule)
}
function isBlocklessAfterSameNameBlockless() {
return !hasBlock(atRule)
&& previousNode
&& !hasBlock(previousNode)
&& previousNode.type === "atrule"
&& previousNode.name == atRule.name
}
function isAfterSameName() {
return previousNode
&& previousNode.type === "atrule"
&& previousNode.name === atRule.name
}
function isFirstNested() {
return isNested
&& atRule === atRule.parent.first
}
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,91 @@
# at-rule-name-case
Specify lowercase or uppercase for at-rules names.
```css
@media (min-width: 10px) {}
/** ↑
* These at-rule names */
```
Only lowercase at-rule names are valid in SCSS.
## Options
`string`: `"lower"|"upper"`
### `"lower"`
The following patterns are considered warnings:
```css
@Charset 'UTF-8';
```
```css
@cHarSeT 'UTF-8';
```
```css
@CHARSET 'UTF-8';
```
```css
@Media (min-width: 50em) {}
```
```css
@mEdIa (min-width: 50em) {}
```
```css
@MEDIA (min-width: 50em) {}
```
The following patterns are *not* considered warnings:
```css
@charset 'UTF-8';
```
```css
@media (min-width: 50em) {}
```
### `"upper"`
The following patterns are considered warnings:
```css
@Charset 'UTF-8';
```
```css
@cHarSeT 'UTF-8';
```
```css
@charset 'UTF-8';
```
```css
@Media (min-width: 50em) {}
```
```css
@mEdIa (min-width: 50em) {}
```
```css
@media (min-width: 50em) {}
```
The following patterns are *not* considered warnings:
```css
@CHARSET 'UTF-8';
```
```css
@MEDIA (min-width: 50em) {}
```

View File

@@ -0,0 +1,49 @@
"use strict"
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "at-rule-name-case"
const messages = ruleMessages(ruleName, {
expected: (actual, expected) => `Expected "${actual}" to be "${expected}"`,
})
const rule = function (expectation) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"lower",
"upper",
],
})
if (!validOptions) {
return
}
root.walkAtRules(atRule => {
const name = atRule.name
const expectedName = expectation === "lower"
? name.toLowerCase()
: name.toUpperCase()
if (name === expectedName) {
return
}
report({
message: messages.expected(name, expectedName),
node: atRule,
ruleName,
result,
})
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,94 @@
# at-rule-name-newline-after
Require a newline after at-rule names.
```css
@media
/*↑*/ (max-width: 600px) {}
/** ↑
* The newline after this at-rule name */
```
## Options
`string`: `"always"|"always-multi-line"`
### `"always"`
There *must always* be a newline after at-rule names.
The following patterns are considered warnings:
```css
@charset "UTF-8";
```
```css
@media (min-width: 700px) and
(orientation: landscape) {}
```
The following patterns are *not* considered warnings:
```css
@charset
"UTF-8";
```
```css
@import
"x.css" screen and
(orientation:landscape);
```
```css
@media
(min-width: 700px) and (orientation: landscape) {}
```
```css
@media
(min-width: 700px) and
(orientation: landscape) {}
```
### `"always-multi-line"`
There *must always* be a newline after at-rule names in at-rules with multi-line parameters.
The following patterns are considered warnings:
```css
@import "x.css" screen and
(orientation:landscape);
```
```css
@media (min-width: 700px) and
(orientation: landscape) {}
```
The following patterns are *not* considered warnings:
```css
@charset "UTF-8";
```
```css
@charset
"UTF-8";
```
```css
@import "x.css" screen and (orientation:landscape);
```
```css
@media (min-width: 700px) and (orientation: landscape) {}
```
```css
@media
(min-width: 700px) and
(orientation: landscape) {}
```

View File

@@ -0,0 +1,39 @@
"use strict"
const atRuleNameSpaceChecker = require("../atRuleNameSpaceChecker")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const whitespaceChecker = require("../../utils/whitespaceChecker")
const ruleName = "at-rule-name-newline-after"
const messages = ruleMessages(ruleName, {
expectedAfter: name => `Expected newline after at-rule name \"${name}\"`,
})
const rule = function (expectation) {
const checker = whitespaceChecker("newline", expectation, messages)
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"always",
"always-multi-line",
],
})
if (!validOptions) {
return
}
atRuleNameSpaceChecker({
root,
result,
locationChecker: checker.afterOneOnly,
checkedRuleName: ruleName,
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,98 @@
# at-rule-name-space-after
Require a single space after at-rule names.
```css
@media (max-width: 600px) {}
/** ↑
* The space after at-rule names */
```
## Options
`string`: `"always"|"always-single-line"`
### `"always"`
There *must always* be a single space after at-rule names.
The following patterns are considered warnings:
```css
@charset"UTF-8";
```
```css
@media(min-width: 700px) {}
```
```css
@media (min-width: 700px) {}
```
```css
@media
(min-width: 700px) {}
```
The following patterns are *not* considered warnings:
```css
@charset "UTF-8";
```
```css
@import url("x.css");
```
```css
@media (min-width: 700px) {}
```
### `"always-single-line"`
There *must always* be a single space after at-rule names in single-line declaration blocks.
The following patterns are considered warnings:
```css
@charset"UTF-8";
```
```css
@media(min-width: 700px) {}
```
```css
@media (min-width: 700px) {}
```
The following patterns are *not* considered warnings:
```css
@charset "UTF-8";
```
```css
@import url("x.css");
```
```css
@media (min-width: 700px) {}
```
```css
@media
(min-width: 700px) {}
```
```css
@media(min-width: 700px) and
(orientation: portrait) {}
```
```css
@media
(min-width: 700px) and
(orientation: portrait) {}
```

View File

@@ -0,0 +1,39 @@
"use strict"
const atRuleNameSpaceChecker = require("../atRuleNameSpaceChecker")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const whitespaceChecker = require("../../utils/whitespaceChecker")
const ruleName = "at-rule-name-space-after"
const messages = ruleMessages(ruleName, {
expectedAfter: name => `Expected single space after at-rule name \"${name}\"`,
})
const rule = function (expectation) {
const checker = whitespaceChecker("space", expectation, messages)
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"always",
"always-single-line",
],
})
if (!validOptions) {
return
}
atRuleNameSpaceChecker({
root,
result,
locationChecker: checker.after,
checkedRuleName: ruleName,
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,65 @@
# at-rule-no-unknown
Disallow unknown at-rules.
```css
@unknown (max-width: 960px) {}
/** ↑
* At-rules like this */
```
This rule considers at-rules defined in the CSS Specifications, up to and including Editor's Drafts, to be known.
## Options
### `true`
The following patterns are considered warnings:
```css
@unknown {}
```
The following patterns are *not* considered warnings:
```css
@charset "UTF-8";
```
```css
@CHARSET "UTF-8";
```
```css
@media (max-width: 960px) {}
```
```css
@font-feature-values Font One {
@styleset {}
}
```
## Optional secondary options
### `ignoreAtRules: ["/regex/", "string"]`
Given:
```js
["/^my-/", "custom"]
```
The following patterns are *not* considered warnings:
```css
@my-at-rule "x.css";
```
```css
@my-other-at-rule {}
```
```css
@custom {}
```

View File

@@ -0,0 +1,58 @@
"use strict"
const _ = require("lodash")
const keywordSets = require("../../reference/keywordSets")
const optionsMatches = require("../../utils/optionsMatches")
const postcss = require("postcss")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "at-rule-no-unknown"
const messages = ruleMessages(ruleName, {
rejected: atRule => `Unexpected unknown at-rule "${atRule}"`,
})
const rule = function (actual, options) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual }, {
actual: options,
possible: {
ignoreAtRules: [_.isString],
},
optional: true,
})
if (!validOptions) {
return
}
root.walkAtRules(atRule => {
const name = atRule.name
// Return early if at-rule is to be ignored
if (optionsMatches(options, "ignoreAtRules", atRule.name)) {
return
}
if (
postcss.vendor.prefix(name)
|| keywordSets.atRules.has(name.toLowerCase())
) {
return
}
report({
message: messages.rejected(`@${name}`),
node: atRule,
ruleName,
result,
})
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,33 @@
# at-rule-no-vendor-prefix
Disallow vendor prefixes for at-rules.
```css
@-webkit-keyframes { 0% { top: 0; } }
/** ↑
* These prefixes */
```
## Options
### `true`
The following patterns are considered warnings:
```css
@-webkit-keyframes { 0% { top: 0; } }
```
```css
@-ms-viewport { orientation: landscape; }
```
The following patterns are *not* considered warnings:
```css
@keyframes { 0% { top: 0; } }
```
```css
@viewport { orientation: landscape; }
```

View File

@@ -0,0 +1,49 @@
"use strict"
const isAutoprefixable = require("../../utils/isAutoprefixable")
const isStandardSyntaxAtRule = require("../../utils/isStandardSyntaxAtRule")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "at-rule-no-vendor-prefix"
const messages = ruleMessages(ruleName, {
rejected: p => `Unexpected vendor-prefixed at-rule "@${p}"`,
})
const rule = function (actual) {
return function (root, result) {
const validOptions = validateOptions(result, ruleName, { actual })
if (!validOptions) {
return
}
root.walkAtRules(atRule => {
if (!isStandardSyntaxAtRule(atRule)) {
return
}
const name = atRule.name
if (name[0] !== "-") {
return
}
if (!isAutoprefixable.atRuleName(name)) {
return
}
report({
message: messages.rejected(name),
node: atRule,
result,
ruleName,
})
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,54 @@
# at-rule-semicolon-newline-after
Require a newline after the semicolon of at-rules.
```css
@import url("x.css");
@import url("y.css");
/** ↑
* The newline after these semicolons */
```
This rule allows an end-of-line comment followed by a newline. For example:
```css
@import url("x.css"); /* end-of-line comment */
a {}
```
## Options
`string`: `"always"`
### `"always"`
There *must always* be a newline after the semicolon.
The following patterns are considered warnings:
```css
@import url("x.css"); @import url("y.css");
```
```css
@import url("x.css"); a {}
```
The following patterns are *not* considered warnings:
```css
@import url("x.css");
@import url("y.css");
```
```css
@import url("x.css"); /* end-of-line comment */
a {}
```
```css
@import url("x.css");
a {}
```

View File

@@ -0,0 +1,63 @@
"use strict"
const hasBlock = require("../../utils/hasBlock")
const nextNonCommentNode = require("../../utils/nextNonCommentNode")
const rawNodeString = require("../../utils/rawNodeString")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const whitespaceChecker = require("../../utils/whitespaceChecker")
const ruleName = "at-rule-semicolon-newline-after"
const messages = ruleMessages(ruleName, {
expectedAfter: () => "Expected newline after \";\"",
})
const rule = function (actual) {
const checker = whitespaceChecker("newline", actual, messages)
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual,
possible: ["always"],
})
if (!validOptions) {
return
}
root.walkAtRules(atRule => {
const nextNode = atRule.next()
if (!nextNode) {
return
}
if (hasBlock(atRule)) {
return
}
// Allow an end-of-line comment
const nodeToCheck = nextNonCommentNode(nextNode)
if (!nodeToCheck) {
return
}
checker.afterOneOnly({
source: rawNodeString(nodeToCheck),
index: -1,
err: msg => {
report({
message: msg,
node: atRule,
index: atRule.toString().length + 1,
result,
ruleName,
})
},
})
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,57 @@
# at-rule-whitelist
Specify a whitelist of allowed at-rules.
```css
@keyframes name {}
/** ↑
* At-rules like this */
```
## Options
`array|string`: `["array", "of", "unprefixed", "at-rules"]|"at-rule"`
Given:
```js
["extend", "keyframes"]
```
The following patterns are considered warnings:
```css
@import "path/to/file.css";
```
```css
@media screen and (max-width: 1024px) {
a { display: none; }
}
```
The following patterns are *not* considered warnings:
```css
a { @extend placeholder; }
```
```css
@keyframes name {
from { top: 10px; }
to { top: 20px; }
}
```
```css
@KEYFRAMES name {
from { top: 10px; }
to { top: 20px; }
}
```
```css
@-moz-keyframes name {
from { top: 10px; }
to { top: 20px; }
}

View File

@@ -0,0 +1,48 @@
"use strict"
const _ = require("lodash")
const postcss = require("postcss")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "at-rule-whitelist"
const messages = ruleMessages(ruleName, {
rejected: name => `Unexpected at-rule "${name}"`,
})
const rule = function (whitelistInput) {
// To allow for just a string as a parameter (not only arrays of strings)
const whitelist = [].concat(whitelistInput)
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: whitelist,
possible: [_.isString],
})
if (!validOptions) {
return
}
root.walkAtRules(atRule => {
const name = atRule.name
if (whitelist.indexOf(postcss.vendor.unprefixed(name).toLowerCase()) !== -1) {
return
}
report({
message: messages.rejected(name),
node: atRule,
result,
ruleName,
})
})
}
}
rule.primaryOptionArray = true
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,29 @@
"use strict"
const isStandardSyntaxAtRule = require("../utils/isStandardSyntaxAtRule")
const report = require("../utils/report")
module.exports = function (options) {
options.root.walkAtRules(atRule => {
if (!isStandardSyntaxAtRule(atRule)) {
return
}
checkColon(`@${atRule.name}${(atRule.raws.afterName || "")}${atRule.params}`, atRule.name.length, atRule)
})
function checkColon(source, index, node) {
options.locationChecker({
source,
index,
err: m => report({
message: m,
node,
index,
result: options.result,
ruleName: options.checkedRuleName,
}),
errTarget: `@${node.name}`,
})
}
}

View File

@@ -0,0 +1,62 @@
# block-closing-brace-empty-line-before
Require or disallow an empty line before the closing brace of blocks.
```css
a {
color: pink;
/* ← */
} /* ↑ */
/** ↑
* This line */
```
## Options
`string`: `"always-multi-line"|"never"`
### `always-multi-line`
The following patterns are considered warnings:
```css
a {
color: pink;
}
```
The following patterns are *not* considered warnings:
```css
a {
color: pink;
}
```
```css
a { color: pink; }
```
### `never`
The following patterns are considered warnings:
```css
a {
color: pink;
}
```
The following patterns are *not* considered warnings:
```css
a {
color: pink;
}
```
```css
a { color: pink; }
```

View File

@@ -0,0 +1,89 @@
"use strict"
const blockString = require("../../utils/blockString")
const hasBlock = require("../../utils/hasBlock")
const hasEmptyBlock = require("../../utils/hasEmptyBlock")
const hasEmptyLine = require("../../utils/hasEmptyLine")
const isSingleLineString = require("../../utils/isSingleLineString")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "block-closing-brace-empty-line-before"
const messages = ruleMessages(ruleName, {
expected: "Expected empty line before closing brace",
rejected: "Unexpected empty line before closing brace",
})
const rule = function (expectation) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"always-multi-line",
"never",
],
})
if (!validOptions) {
return
}
// Check both kinds of statements: rules and at-rules
root.walkRules(check)
root.walkAtRules(check)
function check(statement) {
// Return early if blockless or has empty block
if (
!hasBlock(statement)
|| hasEmptyBlock(statement)
) {
return
}
// Get whitespace after ""}", ignoring extra semicolon
const before = (statement.raws.after || "").replace(/;+/, "")
if (before === undefined) {
return
}
// Calculate index
const statementString = statement.toString()
let index = statementString.length - 1
if (statementString[index - 1] === "\r") {
index -= 1
}
// Set expectation
const expectEmptyLineBefore = expectation === "always-multi-line"
&& !isSingleLineString(blockString(statement))
? true
: false
// Check for at least one empty line
const hasEmptyLineBefore = hasEmptyLine(before)
// Return if the expectation is met
if (expectEmptyLineBefore === hasEmptyLineBefore) {
return
}
const message = expectEmptyLineBefore
? messages.expected
: messages.rejected
report({
message,
result,
ruleName,
node: statement,
index,
})
}
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,174 @@
# block-closing-brace-newline-after
Require a newline or disallow whitespace after the closing brace of blocks.
```css
a { color: pink; }
a { color: red; }
/** ↑
* The newline after this brace */
```
This rule allows an end-of-line comment separated from the closing brace by spaces, as long as the comment contains no newlines. For example,
```css
a {
color: pink;
} /* end-of-line comment */
```
This rule allows a trailing semicolon after the closing brace of a block. For example,
```css
:root {
--toolbar-theme: {
background-color: hsl(120, 70%, 95%);
};
/* ↑
* This semicolon */
}
```
## Options
`string`: `"always"|"always-single-line"|"never-single-line"|"always-multi-line"|"never-multi-line"`
### `"always"`
There *must always* be a newline after the closing brace.
The following patterns are considered warnings:
```css
a { color: pink; }b { color: red; }
```
```css
a { color: pink;
} b { color: red; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }
b { color: red; }
```
### `"always-single-line"`
There *must always* be a newline after the closing brace in single-line blocks.
The following patterns are considered warnings:
```css
a { color: pink; } b { color: red; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink;
} b { color: red; }
```
```css
a { color: pink; }
b { color: red; }
```
### `"never-single-line"`
There *must never* be whitespace after the closing brace in single-line blocks.
The following patterns are considered warnings:
```css
a { color: pink; } b { color: red; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }b { color: red; }
```
```css
a { color: pink;
} b { color: red; }
```
### `"always-multi-line"`
There *must always* be a newline after the closing brace in multi-line blocks.
The following patterns are considered warnings:
```css
a { color: pink;
}b { color: red; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }b { color: red; }
```
```css
a { color: pink;
}
b { color: red; }
```
### `"never-multi-line"`
There *must never* be whitespace after the closing brace in multi-line blocks.
The following patterns are considered warnings:
```css
a { color: pink;
} b { color: red; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; } b { color: red; }
```
```css
a { color: pink;
}b { color: red; }
```
## Optional secondary options
### `ignoreAtRules: ["/regex/", "non-regex"]`
Ignore specified at-rules.
For example, with `"always"` or `"always-multi-line"`.
Given:
```js
["if", "else"]
```
The following patterns are *not* considered warnings:
```css
@if ($var) {
color: pink;
} @else if ($var2) {
color: red;
} @else {
color: blue;
}
```
```css
@if ($var) { color: pink; } @else { color: blue; }
```

View File

@@ -0,0 +1,110 @@
"use strict"
const _ = require("lodash")
const blockString = require("../../utils/blockString")
const hasBlock = require("../../utils/hasBlock")
const optionsMatches = require("../../utils/optionsMatches")
const rawNodeString = require("../../utils/rawNodeString")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const whitespaceChecker = require("../../utils/whitespaceChecker")
const ruleName = "block-closing-brace-newline-after"
const messages = ruleMessages(ruleName, {
expectedAfter: () => "Expected newline after \"}\"",
expectedAfterSingleLine: () => "Expected newline after \"}\" of a single-line block",
rejectedAfterSingleLine: () => "Unexpected whitespace after \"}\" of a single-line block",
expectedAfterMultiLine: () => "Expected newline after \"}\" of a multi-line block",
rejectedAfterMultiLine: () => "Unexpected whitespace after \"}\" of a multi-line block",
})
const rule = function (expectation, options) {
const checker = whitespaceChecker("newline", expectation, messages)
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"always",
"always-single-line",
"never-single-line",
"always-multi-line",
"never-multi-line",
],
}, {
actual: options,
possible: {
ignoreAtRules: [_.isString],
},
optional: true,
})
if (!validOptions) {
return
}
// Check both kinds of statements: rules and at-rules
root.walkRules(check)
root.walkAtRules(check)
function check(statement) {
if (!hasBlock(statement)) {
return
}
if (optionsMatches(options, "ignoreAtRules", statement.name)) {
return
}
const nextNode = statement.next()
if (!nextNode) {
return
}
// Allow an end-of-line comment x spaces after the brace
const nextNodeIsSingleLineComment = nextNode.type === "comment"
&& !/[^ ]/.test((nextNode.raws.before || ""))
&& nextNode.toString().indexOf("\n") === -1
const nodeToCheck = nextNodeIsSingleLineComment
? nextNode.next()
: nextNode
if (!nodeToCheck) {
return
}
let reportIndex = statement.toString().length
let source = rawNodeString(nodeToCheck)
// Skip a semicolon at the beginning, if any
if (
source
&& source[0] === ";"
) {
source = source.slice(1)
reportIndex++
}
// Only check one after, because there might be other
// spaces handled by the indentation rule
checker.afterOneOnly({
source,
index: -1,
lineCheckStr: blockString(statement),
err: msg => {
report({
message: msg,
node: statement,
index: reportIndex,
result,
ruleName,
})
},
})
}
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,81 @@
# block-closing-brace-newline-before
Require a newline or disallow whitespace before the closing brace of blocks.
```css
a { color: pink;
}
/** ↑
* The newline before this brace */
```
## Options
`string`: `"always"|"always-multi-line"|"never-multi-line"`
### `"always"`
There *must always* be a newline before the closing brace.
The following patterns are considered warnings:
```css
a { color: pink;}
```
The following patterns are *not* considered warnings:
```css
a { color: pink;
}
```
```css
a {
color: pink;
}
```
### `"always-multi-line"`
There *must always* be a newline before the closing brace in multi-line blocks.
The following patterns are considered warnings:
```css
a {
color: pink;}
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }
```
```css
a { color: pink;
}
```
### `"never-multi-line"`
There *must never* be whitespace before the closing brace in multi-line blocks.
The following patterns are considered warnings:
```css
a {
color: pink; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }
```
```css
a {
color: pink;}
```

View File

@@ -0,0 +1,103 @@
"use strict"
const _ = require("lodash")
const blockString = require("../../utils/blockString")
const hasBlock = require("../../utils/hasBlock")
const hasEmptyBlock = require("../../utils/hasEmptyBlock")
const isSingleLineString = require("../../utils/isSingleLineString")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "block-closing-brace-newline-before"
const messages = ruleMessages(ruleName, {
expectedBefore: "Expected newline before \"}\"",
expectedBeforeMultiLine: "Expected newline before \"}\" of a multi-line block",
rejectedBeforeMultiLine: "Unexpected whitespace before \"}\" of a multi-line block",
})
const rule = function (expectation) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"always",
"always-multi-line",
"never-multi-line",
],
})
if (!validOptions) {
return
}
// Check both kinds of statements: rules and at-rules
root.walkRules(check)
root.walkAtRules(check)
function check(statement) {
// Return early if blockless or has empty block
if (
!hasBlock(statement)
|| hasEmptyBlock(statement)
) {
return
}
// Ignore extra semicolon
const after = (statement.raws.after || "").replace(/;+/, "")
if (after === undefined) {
return
}
const blockIsMultiLine = !isSingleLineString(blockString(statement))
const statementString = statement.toString()
let index = statementString.length - 2
if (statementString[index - 1] === "\r") {
index -= 1
}
// We're really just checking whether a
// newline *starts* the block's final space -- between
// the last declaration and the closing brace. We can
// ignore any other whitespace between them, because that
// will be checked by the indentation rule.
if (
!_.startsWith(after, "\n")
&& !_.startsWith(after, "\r\n")
) {
if (expectation === "always") {
complain(messages.expectedBefore)
} else if (
blockIsMultiLine
&& expectation === "always-multi-line"
) {
complain(messages.expectedBeforeMultiLine)
}
}
if (
after !== ""
&& blockIsMultiLine
&& expectation === "never-multi-line"
) {
complain(messages.rejectedBeforeMultiLine)
}
function complain(message) {
report({
message,
result,
ruleName,
node: statement,
index,
})
}
}
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,158 @@
# block-closing-brace-space-after
Require a single space or disallow whitespace after the closing brace of blocks.
```css
a { color: pink; }
/** ↑
* The space after this brace */
```
This rule allows a trailing semicolon after the closing brace of a block. For example,
```css
:root {
--toolbar-theme: {
background-color: hsl(120, 70%, 95%);
};
/* ↑
* This semicolon */
}
```
## Options
`string`: `"always"|"never"|"always-single-line"|"never-single-line"|"always-multi-line"|"never-multi-line"`
### `"always"`
There *must always* be a single space after the closing brace.
The following patterns are considered warnings:
```css
a { color: pink; }b { color: red; }
```
```css
a { color: pink; }
b { color: red; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; } b { color: red; }
```
### `"never"`
There *must never* be whitespace after the closing brace.
The following patterns are considered warnings:
```css
a { color: pink; } b { color: red; }
```
```css
a { color: pink; }
b { color: red; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }b { color: red; }
```
```css
a { color: pink;
}b { color: red; }
```
### `"always-single-line"`
There *must always* be a single space after the closing brace in single-line blocks.
The following patterns are considered warnings:
```css
a { color: pink; }b { color: red; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; } b { color: red; }
```
```css
a { color: pink;
}b { color: red; }
```
### `"never-single-line"`
There *must never* be whitespace after the closing brace in single-line blocks.
The following patterns are considered warnings:
```css
a { color: pink; } b { color: red; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }b { color: red; }
```
```css
a { color: pink;
} b { color: red; }
```
### `"always-multi-line"`
There *must always* be a single space after the closing brace in multi-line blocks.
The following patterns are considered warnings:
```css
a { color: pink;
}b { color: red; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }b { color: red; }
```
```css
a { color: pink;
} b { color: red; }
```
### `"never-multi-line"`
There *must never* be whitespace after the closing brace in multi-line blocks.
The following patterns are considered warnings:
```css
a { color: pink;
} b { color: red; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; } b { color: red; }
```
```css
a { color: pink;
}b { color: red; }
```

View File

@@ -0,0 +1,86 @@
"use strict"
const blockString = require("../../utils/blockString")
const hasBlock = require("../../utils/hasBlock")
const rawNodeString = require("../../utils/rawNodeString")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const whitespaceChecker = require("../../utils/whitespaceChecker")
const ruleName = "block-closing-brace-space-after"
const messages = ruleMessages(ruleName, {
expectedAfter: () => "Expected single space after \"}\"",
rejectedAfter: () => "Unexpected whitespace after \"}\"",
expectedAfterSingleLine: () => "Expected single space after \"}\" of a single-line block",
rejectedAfterSingleLine: () => "Unexpected whitespace after \"}\" of a single-line block",
expectedAfterMultiLine: () => "Expected single space after \"}\" of a multi-line block",
rejectedAfterMultiLine: () => "Unexpected whitespace after \"}\" of a multi-line block",
})
const rule = function (expectation) {
const checker = whitespaceChecker("space", expectation, messages)
return function (root, result) {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"always",
"never",
"always-single-line",
"never-single-line",
"always-multi-line",
"never-multi-line",
],
})
if (!validOptions) {
return
}
// Check both kinds of statements: rules and at-rules
root.walkRules(check)
root.walkAtRules(check)
function check(statement) {
const nextNode = statement.next()
if (!nextNode) {
return
}
if (!hasBlock(statement)) {
return
}
let reportIndex = statement.toString().length
let source = rawNodeString(nextNode)
// Skip a semicolon at the beginning, if any
if (
source
&& source[0] === ";"
) {
source = source.slice(1)
reportIndex++
}
checker.after({
source,
index: -1,
lineCheckStr: blockString(statement),
err: msg => {
report({
message: msg,
node: statement,
index: reportIndex,
result,
ruleName,
})
},
})
}
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,151 @@
# block-closing-brace-space-before
Require a single space or disallow whitespace before the closing brace of blocks.
```css
a { color: pink; }
/** ↑
* The space before this brace */
```
## Options
`string`: `"always"|"never"|"always-single-line"|"never-single-line"|"always-multi-line"|"never-multi-line"`
### `"always"`
There *must always* be a single space before the closing brace.
The following patterns are considered warnings:
```css
a { color: pink;}
```
```css
a
{ color: pink;}
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }
```
```css
a {
color: pink; }
```
### `"never"`
There *must never* be whitespace before the closing brace.
The following patterns are considered warnings:
```css
a { color: pink; }
```
```css
a
{ color: pink; }
```
The following patterns are *not* considered warnings:
```css
a{ color: pink;}
```
```css
a{
color: pink;}
```
### `"always-single-line"`
There *must always* be a single space before the closing brace in single-line blocks.
The following patterns are considered warnings:
```css
a { color: pink;}
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }
```
```css
a {
color: pink;}
```
### `"never-single-line"`
There *must never* be whitespace before the closing brace in single-line blocks.
The following patterns are considered warnings:
```css
a { color: pink; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink;}
```
```css
a {
color: pink; }
```
### `"always-multi-line"`
There *must always* be a single space before the closing brace in multi-line blocks.
The following patterns are considered warnings:
```css
a {
color: pink;}
```
The following patterns are *not* considered warnings:
```css
a { color: pink;}
```
```css
a {
color: pink; }
```
### `"never-multi-line"`
There *must never* be whitespace before the closing brace in multi-line blocks.
The following patterns are considered warnings:
```css
a {
color: pink; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }
```
```css
a {
color: pink;}
```

View File

@@ -0,0 +1,81 @@
"use strict"
const blockString = require("../../utils/blockString")
const hasBlock = require("../../utils/hasBlock")
const hasEmptyBlock = require("../../utils/hasEmptyBlock")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const whitespaceChecker = require("../../utils/whitespaceChecker")
const ruleName = "block-closing-brace-space-before"
const messages = ruleMessages(ruleName, {
expectedBefore: () => "Expected single space before \"}\"",
rejectedBefore: () => "Unexpected whitespace before \"}\"",
expectedBeforeSingleLine: () => "Expected single space before \"}\" of a single-line block",
rejectedBeforeSingleLine: () => "Unexpected whitespace before \"}\" of a single-line block",
expectedBeforeMultiLine: () => "Expected single space before \"}\" of a multi-line block",
rejectedBeforeMultiLine: () => "Unexpected whitespace before \"}\" of a multi-line block",
})
const rule = function (expectation) {
const checker = whitespaceChecker("space", expectation, messages)
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual : expectation,
possible : [
"always",
"never",
"always-single-line",
"never-single-line",
"always-multi-line",
"never-multi-line",
],
})
if (!validOptions) {
return
}
// Check both kinds of statement: rules and at-rules
root.walkRules(check)
root.walkAtRules(check)
function check(statement) {
// Return early if blockless or has empty block
if (
!hasBlock(statement)
|| hasEmptyBlock(statement)
) {
return
}
const source = blockString(statement)
const statementString = statement.toString()
let index = statementString.length - 2
if (statementString[index - 1] === "\r") {
index -= 1
}
checker.before({
source,
index: source.length - 1,
err: msg => {
report({
message: msg,
node: statement,
index,
result,
ruleName,
})
},
})
}
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,37 @@
# block-no-empty
Disallow empty blocks.
```css
a { }
/** ↑
* Blocks like this */
```
## Options
### `true`
The following patterns are considered warnings:
```css
a {}
```
```css
a { }
```
```css
@media print { a {} }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }
```
```css
@media print { a { color: pink; } }
```

View File

@@ -0,0 +1,51 @@
"use strict"
const beforeBlockString = require("../../utils/beforeBlockString")
const hasEmptyBlock = require("../../utils/hasEmptyBlock")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "block-no-empty"
const messages = ruleMessages(ruleName, {
rejected: "Unexpected empty block",
})
const rule = function (actual) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual })
if (!validOptions) {
return
}
// Check both kinds of statements: rules and at-rules
root.walkRules(check)
root.walkAtRules(check)
function check(statement) {
if (!hasEmptyBlock(statement)) {
return
}
let index = beforeBlockString(statement, { noRawBefore: true }).length
// For empty blocks when using SugarSS parser
if (statement.raws.between === undefined) {
index--
}
report({
message: messages.rejected,
node: statement,
index,
result,
ruleName,
})
}
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,78 @@
# block-no-single-line
***Deprecated: instead use the [block-opening-brace-newline-after](../block-opening-brace-newline-after/README.md#always) and [block-closing-brace-newline-before](../block-closing-brace-newline-before/README.md#always) rules with the option `"always"`. See [the FAQs for an example](../../../docs/user-guide/faq.md#how-do-i-disallow-single-line-blocks).***
Disallow single-line blocks.
```css
a { color: pink; top: 0; }
/** ↑ ↑
* Declaration blocks like this */
```
## Options
### `true`
The following patterns are considered warnings:
```css
a { color: pink; }
```
```css
a,
b { color: pink; }
```
```css
a { color: pink; top: 1px; }
```
```css
@media print { a { color: pink; } }
```
```css
@media print {
a { color: pink; }
}
```
```css
a {
color: red;
@media print { color: pink; }
}
```
The following patterns are *not* considered warnings:
```css
a {
color: pink;
}
```
```css
a, b {
color: pink;
}
```
```css
@media print {
a {
color: pink;
}
}
```
```css
a {
color: red;
@media print {
color: pink;
}
}
```

View File

@@ -0,0 +1,61 @@
"use strict"
const beforeBlockString = require("../../utils/beforeBlockString")
const blockString = require("../../utils/blockString")
const hasBlock = require("../../utils/hasBlock")
const hasEmptyBlock = require("../../utils/hasEmptyBlock")
const isSingleLineString = require("../../utils/isSingleLineString")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "block-no-single-line"
const messages = ruleMessages(ruleName, {
rejected: "Unexpected single-line block",
})
const rule = function (actual) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual })
if (!validOptions) {
return
}
result.warn((
"'block-no-single-line' has been deprecated and in 8.0 will be removed. " +
"Instead use 'block-opening-brace-newline-after' and 'block-closing-brace-newline-before' with the \"always\" option."
), {
stylelintType: "deprecation",
stylelintReference: "https://stylelint.io/user-guide/rules/block-no-single-line/",
})
// Check both kinds of statements: rules and at-rules
root.walkRules(check)
root.walkAtRules(check)
function check(statement) {
if (
!hasBlock(statement)
|| hasEmptyBlock(statement)
) {
return
}
if (!isSingleLineString(blockString(statement))) {
return
}
report({
message: messages.rejected,
node: statement,
index: beforeBlockString(statement, { noRawBefore: true }).length,
result,
ruleName,
})
}
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,109 @@
# block-opening-brace-newline-after
Require a newline after the opening brace of blocks.
```css
a {
color: pink; }
/** ↑
* The newline after this brace */
```
This rule allows an end-of-line comment followed by a newline. For example,
```css
a { /* end-of-line comment */
color: pink;
}
```
Refer to [the FAQ](../../../docs/user-guide/faq.md#how-do-i-disallow-single-line-blocks) for more information on using this rule with [`block-opening-brace-newline-before`](../block-opening-brace-newline-before/README.md) to disallow single-line rules.
## Options
`string`: `"always"|"always-multi-line"|"never-multi-line"`
### `"always"`
There *must always* be a newline after the opening brace.
The following patterns are considered warnings:
```css
a{ color: pink; }
```
```css
a{ color: pink;
}
```
```css
a{ /* end-of-line comment
with a newline */
color: pink;
}
```
The following patterns are *not* considered warnings:
```css
a {
color: pink; }
```
```css
a
{
color: pink; }
```
```css
a { /* end-of-line comment */
color: pink;
}
```
### `"always-multi-line"`
There *must always* be a newline after the opening brace in multi-line blocks.
The following patterns are considered warnings:
```css
a{color: pink;
}
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }
```
```css
a {
color: pink; }
```
### `"never-multi-line"`
There *must never* be whitespace after the opening brace in multi-line blocks.
The following patterns are considered warnings:
```css
a { color: pink;
}
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }
```
```css
a {color: pink;
}
```

View File

@@ -0,0 +1,77 @@
"use strict"
const beforeBlockString = require("../../utils/beforeBlockString")
const blockString = require("../../utils/blockString")
const hasBlock = require("../../utils/hasBlock")
const hasEmptyBlock = require("../../utils/hasEmptyBlock")
const nextNonCommentNode = require("../../utils/nextNonCommentNode")
const rawNodeString = require("../../utils/rawNodeString")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const whitespaceChecker = require("../../utils/whitespaceChecker")
const ruleName = "block-opening-brace-newline-after"
const messages = ruleMessages(ruleName, {
expectedAfter: () => "Expected newline after \"{\"",
expectedAfterMultiLine: () => "Expected newline after \"{\" of a multi-line block",
rejectedAfterMultiLine: () => "Unexpected whitespace after \"{\" of a multi-line block",
})
const rule = function (expectation) {
const checker = whitespaceChecker("newline", expectation, messages)
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"always",
"always-multi-line",
"never-multi-line",
],
})
if (!validOptions) {
return
}
// Check both kinds of statement: rules and at-rules
root.walkRules(check)
root.walkAtRules(check)
function check(statement) {
// Return early if blockless or has an empty block
if (
!hasBlock(statement)
|| hasEmptyBlock(statement)
) {
return
}
// Allow an end-of-line comment
const nodeToCheck = nextNonCommentNode(statement.first)
if (!nodeToCheck) {
return
}
checker.afterOneOnly({
source: rawNodeString(nodeToCheck),
index: -1,
lineCheckStr: blockString(statement),
err: m => {
report({
message: m,
node: statement,
index: beforeBlockString(statement, { noRawBefore: true }).length + 1,
result,
ruleName,
})
},
})
}
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,153 @@
# block-opening-brace-newline-before
Require a newline or disallow whitespace before the opening brace of blocks.
```css
a
{ color: pink; }
/** ↑
* The newline before this brace */
```
Refer to [the FAQ](../../../docs/user-guide/faq.md#how-do-i-disallow-single-line-blocks) for more information on using this rule with [`block-opening-brace-newline-after`](../block-opening-brace-newline-after/README.md) to disallow single-line rules.
## Options
`string`: `"always"|"always-single-line"|"never-single-line"|"always-multi-line"|"never-multi-line"`
### `"always"`
There *must always* be a newline before the opening brace.
The following patterns are considered warnings:
```css
a{ color: pink; }
```
```css
a{ color: pink;
}
```
The following patterns are *not* considered warnings:
```css
a
{ color: pink; }
```
```css
a
{
color: pink; }
```
```css
a /* foo */
{
color: pink;
}
```
### `"always-single-line"`
There *must always* be a newline before the opening brace in single-line blocks.
The following patterns are considered warnings:
```css
a{ color: pink; }
```
The following patterns are *not* considered warnings:
```css
a
{ color: pink; }
```
```css
a{
color: pink; }
```
### `"never-single-line"`
There *must never* be whitespace before the opening brace in single-line blocks.
The following patterns are considered warnings:
```css
a { color: pink; }
```
The following patterns are *not* considered warnings:
```css
a{ color: pink; }
```
```css
a {
color: pink; }
```
### `"always-multi-line"`
There *must always* be a newline before the opening brace in multi-line blocks.
The following patterns are considered warnings:
```css
a{
color: pink; }
```
```css
a {
color: pink; }
```
The following patterns are *not* considered warnings:
```css
a{ color: pink; }
```
```css
a { color: pink; }
```
```css
a
{ color: pink; }
```
```css
a
{
color: pink; }
```
### `"never-multi-line"`
There *must never* be whitespace before the opening brace in multi-line blocks.
The following patterns are considered warnings:
```css
a {
color: pink; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }
```
```css
a{
color: pink;}
```

View File

@@ -0,0 +1,81 @@
"use strict"
const beforeBlockString = require("../../utils/beforeBlockString")
const blockString = require("../../utils/blockString")
const hasBlock = require("../../utils/hasBlock")
const hasEmptyBlock = require("../../utils/hasEmptyBlock")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const whitespaceChecker = require("../../utils/whitespaceChecker")
const ruleName = "block-opening-brace-newline-before"
const messages = ruleMessages(ruleName, {
expectedBefore: () => "Expected newline before \"{\"",
expectedBeforeSingleLine: () => "Expected newline before \"{\" of a single-line block",
rejectedBeforeSingleLine: () => "Unexpected whitespace before \"{\" of a single-line block",
expectedBeforeMultiLine: () => "Expected newline before \"{\" of a multi-line block",
rejectedBeforeMultiLine: () => "Unexpected whitespace before \"{\" of a multi-line block",
})
const rule = function (expectation) {
const checker = whitespaceChecker("newline", expectation, messages)
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual : expectation,
possible : [
"always",
"always-single-line",
"never-single-line",
"always-multi-line",
"never-multi-line",
],
})
if (!validOptions) {
return
}
// Check both kinds of statement: rules and at-rules
root.walkRules(check)
root.walkAtRules(check)
function check(statement) {
// Return early if blockless or has an empty block
if (
!hasBlock(statement)
|| hasEmptyBlock(statement)
) {
return
}
const source = beforeBlockString(statement)
const beforeBraceNoRaw = beforeBlockString(statement, { noRawBefore: true })
let index = beforeBraceNoRaw.length - 1
if (beforeBraceNoRaw[index - 1] === "\r") {
index -= 1
}
checker.beforeAllowingIndentation({
lineCheckStr: blockString(statement),
source,
index: source.length,
err: m => {
report({
message: m,
node: statement,
index,
result,
ruleName,
})
},
})
}
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,151 @@
# block-opening-brace-space-after
Require a single space or disallow whitespace after the opening brace of blocks.
```css
a { color: pink; }
/** ↑
* The space after this brace */
```
## Options
`string`: `"always"|"never"|"always-single-line"|"never-single-line"|"always-multi-line"|"never-multi-line"`
### `"always"`
There *must always* be a single space after the opening brace.
The following patterns are considered warnings:
```css
a {color: pink; }
```
```css
a {
color: pink; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }
```
```css
a { color: pink;
}
```
### `"never"`
There *must never* be whitespace after the opening brace.
The following patterns are considered warnings:
```css
a { color: pink; }
```
```css
a {
color: pink; }
```
The following patterns are *not* considered warnings:
```css
a {color: pink; }
```
```css
a
{color: pink; }
```
### `"always-single-line"`
There *must always* be a single space after the opening brace in single-line blocks.
The following patterns are considered warnings:
```css
a {color: pink; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }
```
```css
a {color: pink;
}
```
### `"never-single-line"`
There *must never* be whitespace after the opening brace in single-line blocks.
The following patterns are considered warnings:
```css
a { color: pink; }
```
The following patterns are *not* considered warnings:
```css
a {color: pink; }
```
```css
a { color: pink;
}
```
### `"always-multi-line"`
There *must always* be a single space after the opening brace in multi-line blocks.
The following patterns are considered warnings:
```css
a {color: pink;
}
```
The following patterns are *not* considered warnings:
```css
a {color: pink; }
```
```css
a { color: pink;
}
```
### `"never-multi-line"`
There *must never* be whitespace after the opening brace in multi-line blocks.
The following patterns are considered warnings:
```css
a { color: pink;
}
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }
```
```css
a {color: pink;
}
```

View File

@@ -0,0 +1,73 @@
"use strict"
const beforeBlockString = require("../../utils/beforeBlockString")
const blockString = require("../../utils/blockString")
const hasBlock = require("../../utils/hasBlock")
const hasEmptyBlock = require("../../utils/hasEmptyBlock")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const whitespaceChecker = require("../../utils/whitespaceChecker")
const ruleName = "block-opening-brace-space-after"
const messages = ruleMessages(ruleName, {
expectedAfter: () => "Expected single space after \"{\"",
rejectedAfter: () => "Unexpected whitespace after \"{\"",
expectedAfterSingleLine: () => "Expected single space after \"{\" of a single-line block",
rejectedAfterSingleLine: () => "Unexpected whitespace after \"{\" of a single-line block",
expectedAfterMultiLine: () => "Expected single space after \"{\" of a multi-line block",
rejectedAfterMultiLine: () => "Unexpected whitespace after \"{\" of a multi-line block",
})
const rule = function (expectation) {
const checker = whitespaceChecker("space", expectation, messages)
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"always",
"never",
"always-single-line",
"never-single-line",
"always-multi-line",
"never-multi-line",
],
})
if (!validOptions) {
return
}
// Check both kinds of statements: rules and at-rules
root.walkRules(check)
root.walkAtRules(check)
function check(statement) {
// Return early if blockless or has an empty block
if (
!hasBlock(statement)
|| hasEmptyBlock(statement)
) {
return
}
checker.after({
source: blockString(statement),
index: 0,
err: m => {
report({
message: m,
node: statement,
index: beforeBlockString(statement, { noRawBefore: true }).length + 1,
result,
ruleName,
})
},
})
}
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,172 @@
# block-opening-brace-space-before
Require a single space or disallow whitespace before the opening brace of blocks.
```css
a { color: pink; }
/** ↑
* The space before this brace */
```
## Options
`string`: `"always"|"never"|"always-single-line"|"never-single-line"|"always-multi-line"|"never-multi-line"`
### `"always"`
There *must always* be a single space before the opening brace.
The following patterns are considered warnings:
```css
a{ color: pink; }
```
```css
a
{ color: pink; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }
```
```css
a {
color: pink; }
```
### `"never"`
There *must never* be whitespace before the opening brace.
The following patterns are considered warnings:
```css
a { color: pink; }
```
```css
a
{ color: pink; }
```
The following patterns are *not* considered warnings:
```css
a{ color: pink; }
```
```css
a{
color: pink; }
```
### `"always-single-line"`
There *must always* be a single space before the opening brace in single-line blocks.
The following patterns are considered warnings:
```css
a{ color: pink; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }
```
```css
a{
color: pink; }
```
### `"never-single-line"`
There *must never* be whitespace before the opening brace in single-line blocks.
The following patterns are considered warnings:
```css
a { color: pink; }
```
The following patterns are *not* considered warnings:
```css
a{ color: pink; }
```
```css
a {
color: pink; }
```
### `"always-multi-line"`
There *must always* be a single space before the opening brace in multi-line blocks.
The following patterns are considered warnings:
```css
a{
color: pink; }
```
The following patterns are *not* considered warnings:
```css
a{ color: pink; }
```
```css
a {
color: pink; }
```
### `"never-multi-line"`
There *must never* be whitespace before the opening brace in multi-line blocks.
The following patterns are considered warnings:
```css
a {
color: pink; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }
```
```css
a{
color: pink;}
```
## Optional secondary options
### `ignoreAtRules: ["/regex/", "non-regex"]`
Given:
```js
["/fo/"]
```
The following patterns are *not* considered warnings:
```css
@for ...
{}
```
```css
@for ...{}
```

View File

@@ -0,0 +1,95 @@
"use strict"
const _ = require("lodash")
const beforeBlockString = require("../../utils/beforeBlockString")
const blockString = require("../../utils/blockString")
const hasBlock = require("../../utils/hasBlock")
const hasEmptyBlock = require("../../utils/hasEmptyBlock")
const optionsMatches = require("../../utils/optionsMatches")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const whitespaceChecker = require("../../utils/whitespaceChecker")
const ruleName = "block-opening-brace-space-before"
const messages = ruleMessages(ruleName, {
expectedBefore: () => "Expected single space before \"{\"",
rejectedBefore: () => "Unexpected whitespace before \"{\"",
expectedBeforeSingleLine: () => "Expected single space before \"{\" of a single-line block",
rejectedBeforeSingleLine: () => "Unexpected whitespace before \"{\" of a single-line block",
expectedBeforeMultiLine: () => "Expected single space before \"{\" of a multi-line block",
rejectedBeforeMultiLine: () => "Unexpected whitespace before \"{\" of a multi-line block",
})
const rule = function (expectation, options) {
const checker = whitespaceChecker("space", expectation, messages)
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"always",
"never",
"always-single-line",
"never-single-line",
"always-multi-line",
"never-multi-line",
],
}, {
actual: options,
possible: {
ignoreAtRules: [_.isString],
},
optional: true,
})
if (!validOptions) {
return
}
// Check both kinds of statements: rules and at-rules
root.walkRules(check)
root.walkAtRules(check)
function check(statement) {
// Return early if blockless or has an empty block
if (
!hasBlock(statement)
|| hasEmptyBlock(statement)
) {
return
}
// Return early if at-rule is to be ignored
if (optionsMatches(options, "ignoreAtRules", statement.name)) {
return
}
const source = beforeBlockString(statement)
const beforeBraceNoRaw = beforeBlockString(statement, { noRawBefore: true })
let index = beforeBraceNoRaw.length - 1
if (beforeBraceNoRaw[index - 1] === "\r") {
index -= 1
}
checker.before({
source,
index: source.length,
lineCheckStr: blockString(statement),
err: m => {
report({
message: m,
node: statement,
index,
result,
ruleName,
})
},
})
}
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,51 @@
"use strict"
const hasEmptyLine = require("../utils/hasEmptyLine")
const isSingleLineString = require("../utils/isSingleLineString")
const optionsMatches = require("../utils/optionsMatches")
const report = require("../utils/report")
module.exports = function (opts) {
let expectEmptyLineBefore = opts.expectation.indexOf("always") !== -1 ? true : false
// Optionally ignore the expectation if a comment precedes this node
if (optionsMatches(opts.options, "ignore", "after-comment") && opts.rule.prev() && opts.rule.prev().type === "comment") {
return
}
// Ignore if the expectation is for multiple and the rule is single-line
if (opts.expectation.indexOf("multi-line") !== -1 && isSingleLineString(opts.rule.toString())) {
return
}
// Optionally reverse the expectation for the first nested node
if (optionsMatches(opts.options, "except", "first-nested") && opts.rule === opts.rule.parent.first) {
expectEmptyLineBefore = !expectEmptyLineBefore
}
// Optionally reverse the expectation if a rule precedes this node
if (optionsMatches(opts.options, "except", "after-rule") && opts.rule.prev() && opts.rule.prev().type === "rule") {
expectEmptyLineBefore = !expectEmptyLineBefore
}
// Optionally reverse the expectation for single line comments
if (optionsMatches(opts.options, "except", "after-single-line-comment") && opts.rule.prev() && opts.rule.prev().type === "comment" && isSingleLineString(opts.rule.prev().toString())) {
expectEmptyLineBefore = !expectEmptyLineBefore
}
const hasEmptyLineBefore = hasEmptyLine(opts.rule.raws.before)
// Return if the expectation is met
if (expectEmptyLineBefore === hasEmptyLineBefore) {
return
}
const message = expectEmptyLineBefore ? opts.messages.expected : opts.messages.rejected
report({
message,
node: opts.rule,
result: opts.result,
ruleName: opts.checkedRuleName,
})
}

View File

@@ -0,0 +1,49 @@
# color-hex-case
Specify lowercase or uppercase for hex colors.
```css
a { color: #fff }
/** ↑
* These hex colors */
```
## Options
`string`: `"lower"|"upper"`
### `"lower"`
The following patterns are considered warnings:
```css
a { color: #FFF; }
```
The following patterns are *not* considered warnings:
```css
a { color: #000; }
```
```css
a { color: #fff; }
```
### `"upper"`
The following patterns are considered warnings:
```css
a { color: #fff; }
```
The following patterns are *not* considered warnings:
```css
a { color: #000; }
```
```css
a { color: #FFF; }
```

View File

@@ -0,0 +1,61 @@
"use strict"
const blurFunctionArguments = require("../../utils/blurFunctionArguments")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const styleSearch = require("style-search")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "color-hex-case"
const messages = ruleMessages(ruleName, {
expected: (actual, expected) => `Expected "${actual}" to be "${expected}"`,
})
const rule = function (expectation) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"lower",
"upper",
],
})
if (!validOptions) {
return
}
root.walkDecls(decl => {
const declString = blurFunctionArguments(decl.toString(), "url")
styleSearch({ source: declString, target: "#" }, match => {
const hexMatch = /^#[0-9A-Za-z]+/.exec(declString.substr(match.startIndex))
if (!hexMatch) {
return
}
const hexValue = hexMatch[0]
const hexValueLower = hexValue.toLowerCase()
const hexValueUpper = hexValue.toUpperCase()
const expectedHex = expectation === "lower"
? hexValueLower
: hexValueUpper
if (hexValue === expectedHex) {
return
}
report({
message: messages.expected(hexValue, expectedHex),
node: decl,
index: match.startIndex,
result,
ruleName,
})
})
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,61 @@
# color-hex-length
Specify short or long notation for hex colors.
```css
a { color: #fff }
/** ↑
* These hex colors */
```
## Options
`string`: `"short"|"long"`
### `"short"`
The following patterns are considered warnings:
```css
a { color: #ffffff; }
```
```css
a { color: #fffffaa; }
```
The following patterns are *not* considered warnings:
```css
a { color: #fff; }
```
```css
a { color: #fffa; }
```
```css
a { color: #a4a4a4; }
```
### `"long"`
The following patterns are considered warnings:
```css
a { color: #fff; }
```
```css
a { color: #fffa; }
```
The following patterns are *not* considered warnings:
```css
a { color: #ffffff; }
```
```css
a { color: #fffffaa; }
```

View File

@@ -0,0 +1,102 @@
"use strict"
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const styleSearch = require("style-search")
const ruleName = "color-hex-length"
const messages = ruleMessages(ruleName, {
expected: (actual, expected) => `Expected "${actual}" to be "${expected}"`,
})
const rule = function (expectation) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"short",
"long",
],
})
if (!validOptions) {
return
}
root.walkDecls(decl => {
const declString = decl.toString()
styleSearch({ source: declString, target: "#" }, match => {
const hexMatch = /^#[0-9A-Za-z]+/.exec(declString.substr(match.startIndex))
if (!hexMatch) {
return
}
const hexValue = hexMatch[0]
if (
expectation === "long"
&& hexValue.length !== 4
&& hexValue.length !== 5
) {
return
}
if (
expectation === "short"
&& (
hexValue.length < 6
|| !canShrink(hexValue)
)
) {
return
}
const variant = expectation === "long"
? longer
: shorter
report({
message: messages.expected(hexValue, variant(hexValue)),
node: decl,
index: match.startIndex,
result,
ruleName,
})
})
})
}
}
function canShrink(hex) {
hex = hex.toLowerCase()
return hex[1] === hex[2]
&& hex[3] === hex[4]
&& hex[5] === hex[6]
&& (hex.length === 7
|| hex.length === 9
&& hex[7] === hex[8]
)
}
function shorter(hex) {
let hexVariant = "#"
for (let i = 1; i < hex.length; i = i + 2) {
hexVariant += hex[i]
}
return hexVariant
}
function longer(hex) {
let hexVariant = "#"
for (let i = 1; i < hex.length; i++) {
hexVariant += hex[i] + hex[i]
}
return hexVariant
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

159
node_modules/stylelint/lib/rules/color-named/README.md generated vendored Normal file
View File

@@ -0,0 +1,159 @@
# color-named
Require (where possible) or disallow named colors.
```css
a { color: black }
/** ↑
* These named colors */
```
## Options
`string`: `"always-where-possible"|"never"`
### `"always-where-possible"`
Colors *must always*, where possible, be named.
This will warn if a hex (3, 4, 6 and 8 digit), `rgb()`, `rgba()`, `hsl()`, `hsla()`, `hwb()` or `gray()` color can be represented as a named color.
The following patterns are considered warnings:
```css
a { color: #000; }
```
```css
a { color: #f000; }
```
```css
a { color: #ff000000; }
```
```css
a { color: rgb(0, 0, 0); }
```
```css
a { color: rgb(0%, 0%, 0%); }
```
```css
a { color: rgba(0, 0, 0, 0); }
```
```css
a { color: hsl(0, 0%, 0%); }
```
```css
a { color: hwb(0, 0%, 100%); }
```
```css
a { color: gray(0); }
```
The following patterns are *not* considered warnings:
```css
a { color: black; }
```
```css
a { color: rgb(10, 0, 0); }
```
```css
a { color: rgb(0, 0, 0, 0.5); }
```
### `"never"`
Colors *must never* be named.
The following patterns are considered warnings:
```css
a { color: black; }
```
```css
a { color: white; }
```
The following patterns are *not* considered warnings:
```css
a { color: #000; }
```
```css
a { color: rgb(0, 0, 0); }
```
```css
a { color: var(--white); }
```
```scss
a { color: $blue; }
```
```less
a { color: @blue; }
```
## Optional secondary options
### `ignore: ["inside-function"]`
Ignore colors that are inside a function.
For example, with `"never"`.
The following patterns are *not* considered warnings:
```css
a {
color: map-get($colour, blue);
}
```
```css
a {
background-image: url(red);
}
```
### `ignoreProperties: ["/regex/", "string"]`
For example with `"never"`.
Given:
```js
["/^my-/", "composes"]
```
The following patterns are *not* considered warnings:
```css
a {
my-property: red;
}
```
```css
a {
my-other-property: red;
}
```
```css
a {
composes: red from './index.css';
}
```

145
node_modules/stylelint/lib/rules/color-named/index.js generated vendored Normal file
View File

@@ -0,0 +1,145 @@
"use strict"
const _ = require("lodash")
const declarationValueIndex = require("../../utils/declarationValueIndex")
const isStandardSyntaxFunction = require("../../utils/isStandardSyntaxFunction")
const isStandardSyntaxValue = require("../../utils/isStandardSyntaxValue")
const keywordSets = require("../../reference/keywordSets")
const namedColorData = require("../../reference/namedColorData")
const optionsMatches = require("../../utils/optionsMatches")
const propertySets = require("../../reference/propertySets")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const valueParser = require("postcss-value-parser")
const ruleName = "color-named"
const messages = ruleMessages(ruleName, {
expected: (named, original) => `Expected "${original}" to be "${named}"`,
rejected: named => `Unexpected named color "${named}"`,
})
// Todo tested on case insensivity
const NODE_TYPES = [
"word",
"function",
]
const rule = function (expectation, options) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"never",
"always-where-possible",
],
}, {
actual: options,
possible: {
ignoreProperties: [_.isString],
ignore: ["inside-function"],
},
optional: true,
})
if (!validOptions) {
return
}
const namedColors = Object.keys(namedColorData)
root.walkDecls(decl => {
if (propertySets.acceptCustomIdents.has(decl.prop)) {
return
}
// Return early if the property is to be ignored
if (optionsMatches(options, "ignoreProperties", decl.prop)) {
return
}
valueParser(decl.value).walk(node => {
const value = node.value,
type = node.type,
sourceIndex = node.sourceIndex
if (
optionsMatches(options, "ignore", "inside-function")
&& type === "function"
) {
return false
}
if (!isStandardSyntaxFunction(node)) {
return false
}
if (!isStandardSyntaxValue(value)) {
return
}
// Return early if neither a word nor a function
if (NODE_TYPES.indexOf(type) === -1) {
return
}
// Check for named colors for "never" option
if (
expectation === "never"
&& type === "word"
&& namedColors.indexOf(value.toLowerCase()) !== -1
) {
complain(messages.rejected(value), decl, declarationValueIndex(decl) + sourceIndex)
return
}
// Check "always-where-possible" option ...
if (expectation !== "always-where-possible") {
return
}
// First by checking for alternative color function representations ...
if (
type === "function"
&& keywordSets.colorFunctionNames.has(value.toLowerCase())
) {
// Remove all spaces to match what's in `representations`
const normalizedFunctionString = valueParser.stringify(node).replace(/\s+/g, "")
let namedColor
for (let i = 0, l = namedColors.length; i < l; i++) {
namedColor = namedColors[i]
if (namedColorData[namedColor].func.indexOf(normalizedFunctionString.toLowerCase()) !== -1) {
complain(messages.expected(namedColor, normalizedFunctionString), decl, declarationValueIndex(decl) + sourceIndex)
return // Exit as soon as a problem is found
}
}
return
}
// Then by checking for alternative hex representations
let namedColor
for (let i = 0, l = namedColors.length; i < l; i++) {
namedColor = namedColors[i]
if (namedColorData[namedColor].hex.indexOf(value.toLowerCase()) !== -1) {
complain(messages.expected(namedColor, value), decl, declarationValueIndex(decl) + sourceIndex)
return // Exit as soon as a problem is found
}
}
})
})
function complain(message, node, index) {
report({
result,
ruleName,
message,
node,
index,
})
}
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,51 @@
# color-no-hex
Disallow hex colors.
```css
a { color: #333 }
/** ↑
* These hex colors */
```
## Options
### `true`
The following patterns are considered warnings:
```css
a { color: #000; }
```
```css
a { color: #fff1aa; }
```
```css
a { color: #123456aa; }
```
Hex values that are not valid also cause warnings:
```css
a { color: #foobar; }
```
```css
a { color: #0000000000000000; }
```
The following patterns are *not* considered warnings:
```css
a { color: black; }
```
```css
a { color: rgb(0, 0, 0); }
```
```css
a { color: rgba(0, 0, 0, 1); }
```

52
node_modules/stylelint/lib/rules/color-no-hex/index.js generated vendored Normal file
View File

@@ -0,0 +1,52 @@
"use strict"
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const styleSearch = require("style-search")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "color-no-hex"
const messages = ruleMessages(ruleName, {
rejected: hex => `Unexpected hex color "${hex}"`,
})
const rule = function (actual) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual })
if (!validOptions) {
return
}
root.walkDecls(decl => {
const declString = decl.toString()
styleSearch({ source: declString, target: "#" }, match => {
// If there's not a colon, comma, or whitespace character before, we'll assume this is
// not intended to be a hex color, but is instead something like the
// hash in a url() argument
if (!/[:,\s]/.test(declString[match.startIndex - 1])) {
return
}
const hexMatch = /^#[0-9A-Za-z]+/.exec(declString.substr(match.startIndex))
if (!hexMatch) {
return
}
const hexValue = hexMatch[0]
report({
message: messages.rejected(hexValue),
node: decl,
index: match.startIndex,
result,
ruleName,
})
})
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,47 @@
# color-no-invalid-hex
Disallow invalid hex colors.
```css
a { color: #y3 }
/** ↑
* These hex colors */
```
Longhand hex colors can be either 6 or 8 (with alpha channel) hexadecimal characters. And their shorthand variants are 3 and 4 characters respectively.
## Options
### `true`
The following patterns are considered warnings:
```css
a { color: #00; }
```
```css
a { color: #fff1az; }
```
```css
a { color: #12345aa; }
```
The following patterns are *not* considered warnings:
```css
a { color: #000; }
```
```css
a { color: #000f; }
```
```css
a { color: #fff1a0; }
```
```css
a { color: #123450aa; }
```

View File

@@ -0,0 +1,57 @@
"use strict"
const isValidHex = require("../../utils/isValidHex")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const styleSearch = require("style-search")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "color-no-invalid-hex"
const messages = ruleMessages(ruleName, {
rejected: hex => `Unexpected invalid hex color "${hex}"`,
})
const rule = function (actual) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual })
if (!validOptions) {
return
}
root.walkDecls(decl => {
const declString = decl.toString()
styleSearch({ source: declString, target: "#" }, match => {
// If there's not a colon, comma, or whitespace character before, we'll assume this is
// not intended to be a hex color, but is instead something like the
// hash in a url() argument
if (!/[:,\s]/.test(declString[match.startIndex - 1])) {
return
}
const hexMatch = /^#[0-9A-Za-z]+/.exec(declString.substr(match.startIndex))
if (!hexMatch) {
return
}
const hexValue = hexMatch[0]
if (isValidHex(hexValue)) {
return
}
report({
message: messages.rejected(hexValue),
node: decl,
index: match.startIndex,
result,
ruleName,
})
})
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,153 @@
# comment-empty-line-before
Require or disallow an empty line before comments.
```css
a {}
/* ← */
/* comment */ /* ↑ */
/** ↑
* This line */
```
If the comment is the very first node in a stylesheet then it is ignored. Shared-line comments are also ignored.
If you're using a custom syntax which support single-line comments with `//`, those are ignored as well.
**Caveat:** Comments within *selector and value lists* are currently ignored.
## Options
`string`: `"always"|"never"`
### `"always"`
There *must always* be an empty line before comments.
The following patterns are considered warnings:
```css
a {}
/* comment */
```
The following patterns are *not* considered warnings:
```css
a {}
/* comment */
```
```css
a {} /* comment */
```
### `"never"`
There *must never* be an empty line before comments.
The following patterns are considered warnings:
```css
a {}
/* comment */
```
The following patterns are *not* considered warnings:
```css
a {}
/* comment */
```
```css
a {} /* comment */
```
## Optional secondary options
### `except: ["first-nested"]`
Reverse the primary option for comments that are nested and the first child of their parent node.
For example, with `"always"`:
The following patterns are considered warnings:
```css
a {
/* comment */
color: pink;
}
```
The following patterns are *not* considered warnings:
```css
a {
/* comment */
color: pink;
}
```
### `ignore: ["after-comment", "stylelint-commands"]`
#### `"after-comment"`
***Note: This option was previously called `between-comments`.***
Don't require an empty line after a comment.
For example, with `"always"`:
The following patterns are *not* considered warnings:
```css
a {
background: pink;
/* comment */
/* comment */
color: #eee;
}
```
```css
a {
background: pink;
/* comment */
/* comment */
color: #eee;
}
```
#### `"stylelint-commands"`
Ignore comments that deliver commands to stylelint, e.g. `/* stylelint-disable color-no-hex */`.
For example, with `"always"`:
The following patterns are considered warnings:
```css
a {
background: pink;
/* not a stylelint command */
color: #eee;
}
```
The following patterns are *not* considered warnings:
```css
a {
background: pink;
/* stylelint-disable color-no-hex */
color: pink;
}
```

View File

@@ -0,0 +1,135 @@
"use strict"
const hasEmptyLine = require("../../utils/hasEmptyLine")
const optionsMatches = require("../../utils/optionsMatches")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "comment-empty-line-before"
const messages = ruleMessages(ruleName, {
expected: "Expected empty line before comment",
rejected: "Unexpected empty line before comment",
})
const stylelintCommandPrefix = "stylelint-"
const rule = function (expectation, options) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"always",
"never",
],
}, {
actual: options,
possible: {
except: ["first-nested"],
ignore: [
"stylelint-commands",
"stylelint-command",
"between-comments",
"after-comment",
],
},
optional: true,
})
if (!validOptions) {
return
}
if (
optionsMatches(options, "ignore", "between-comments")
) {
result.warn((
"'comment-empty-line-before\'s' \"between-comments\" option has been deprecated and in 8.0 will be removed. " +
"Instead use the \"after-comment\" option."
), {
stylelintType: "deprecation",
stylelintReference: "https://stylelint.io/user-guide/rules/comment-empty-line-before/",
})
}
root.walkComments(comment => {
// Ignore the first node
if (comment === root.first) {
return
}
// Optionally ignore stylelint commands
if (
comment.text.indexOf(stylelintCommandPrefix) === 0
&& optionsMatches(options, "ignore", "stylelint-commands")
) {
return
}
// Optionally ignore newlines between comments
const prev = comment.prev()
if (
prev
&& prev.type === "comment"
&& optionsMatches(options, "ignore", "between-comments")
) {
return
}
if (
prev
&& prev.type === "comment"
&& optionsMatches(options, "ignore", "after-comment")
) {
return
}
if (
comment.raws.inline
|| comment.inline
) {
return
}
const before = (comment.raws.before || "")
// Ignore shared-line comments
if (before.indexOf("\n") === -1) {
return
}
const expectEmptyLineBefore = (() => {
if (
optionsMatches(options, "except", "first-nested")
&& comment.parent !== root
&& comment === comment.parent.first
) {
return false
}
return expectation === "always"
})()
const hasEmptyLineBefore = hasEmptyLine(before)
// Return if the expectation is met
if (expectEmptyLineBefore === hasEmptyLineBefore) {
return
}
const message = expectEmptyLineBefore
? messages.expected
: messages.rejected
report({
message,
node: comment,
result,
ruleName,
})
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,43 @@
# comment-no-empty
Disallow empty comments.
```css
/* */
/** ↑
* Comments like this */
```
**Caveat:** Comments within *selector and value lists* are currently ignored.
## Options
### `true`
The following patterns are considered warnings:
```css
/**/
```
```css
/* */
```
```css
/*
*/
```
The following patterns are *not* considered warnings:
```css
/* comment */
```
```css
/*
* Multi-line Comment
**/
```

View File

@@ -0,0 +1,49 @@
"use strict"
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "comment-no-empty"
const messages = ruleMessages(ruleName, {
rejected: "Unexpected empty comment",
})
const rule = function (actual) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual })
if (!validOptions) {
return
}
root.walkComments(comment => {
// To ignore inline SCSS comments
if (
comment.raws.inline
|| comment.inline
) {
return
}
// To ignore comments that are not empty
if (
comment.text
&& comment.text.length !== 0
) {
return
}
report({
message: messages.rejected,
node: comment,
result,
ruleName,
})
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,84 @@
# comment-whitespace-inside
Require or disallow whitespace on the inside of comment markers.
```css
/* comment */
/** ↑ ↑
* The space inside these two markers */
```
Any number of asterisks are allowed at the beginning or end of the comment. So `/** comment **/` is treated the same way as `/* comment */`.
**Caveat:** Comments within *selector and value lists* are currently ignored.
## Options
`string`: `"always"|"never"`
### `"always"`
There *must always* be whitespace inside the markers.
The following patterns are considered warnings:
```css
/*comment*/
```
```css
/*comment */
```
```css
/** comment**/
```
The following patterns are *not* considered warnings:
```css
/* comment */
```
```css
/** comment **/
```
```css
/**
* comment
*/
```
```css
/* comment
*/
```
### `"never"`
There *must never* be whitespace on the inside the markers.
The following patterns are considered warnings:
```css
/* comment */
```
```css
/*comment */
```
```css
/** comment**/
```
The following patterns are *not* considered warnings:
```css
/*comment*/
```
```css
/****comment****/
```

View File

@@ -0,0 +1,96 @@
"use strict"
const isWhitespace = require("../../utils/isWhitespace")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "comment-whitespace-inside"
const messages = ruleMessages(ruleName, {
expectedOpening: "Expected whitespace after \"/*\"",
rejectedOpening: "Unexpected whitespace after \"/*\"",
expectedClosing: "Expected whitespace before \"*/\"",
rejectedClosing: "Unexpected whitespace before \"*/\"",
})
const rule = function (expectation) {
return function (root, result) {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"always",
"never",
],
})
if (!validOptions) {
return
}
root.walkComments(function (comment) {
if (
comment.raws.inline
|| comment.inline
) {
return
}
const rawComment = comment.toString()
const firstFourChars = rawComment.substr(0, 4)
// Return early if sourcemap or copyright comment
if (/^\/\*[#!]\s/.test(firstFourChars)) {
return
}
const leftMatches = rawComment.match(/(^\/\*+)(\s)?/)
const rightMatches = rawComment.match(/(\s)?(\*+\/)$/)
const opener = leftMatches[1]
const leftSpace = leftMatches[2] || ""
const rightSpace = rightMatches[1] || ""
const closer = rightMatches[2]
if (
expectation === "never"
&& leftSpace !== ""
) {
complain(messages.rejectedOpening, opener.length)
}
if (
expectation === "always"
&& !isWhitespace(leftSpace)
) {
complain(messages.expectedOpening, opener.length)
}
if (
expectation === "never"
&& rightSpace !== ""
) {
complain(messages.rejectedClosing, comment.toString().length - closer.length - 1)
}
if (
expectation === "always"
&& !isWhitespace(rightSpace)
) {
complain(messages.expectedClosing, comment.toString().length - closer.length - 1)
}
function complain(message, index) {
report({
message,
index,
result,
ruleName,
node: comment,
})
}
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,43 @@
# comment-word-blacklist
Specify a blacklist of disallowed words within comments.
```css
/* words within comments */
/** ↑ ↑ ↑
* These three words */
```
**Caveat:** Comments within *selector and value lists* are currently ignored.
## Options
`array|string`: `["array", "of", "words", "or", "/regex/"]|"word"|"/regex/"`
If a string is surrounded with `"/"` (e.g. `"/^TODO:/"`), it is interpreted as a regular expression.
Given:
```js
["/^TODO:/", "badword"]
```
The following patterns are considered warnings:
```css
/* TODO: */
```
```css
/* TODO: add fallback */
```
```css
/* some badword */
```
The following patterns are *not* considered warnings:
```css
/* comment */
```

View File

@@ -0,0 +1,56 @@
"use strict"
const _ = require("lodash")
const containsString = require("../../utils/containsString")
const matchesStringOrRegExp = require("../../utils/matchesStringOrRegExp")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "comment-word-blacklist"
const messages = ruleMessages(ruleName, {
rejected: pattern => `Unexpected word matching pattern "${pattern}"`,
})
const rule = function (blacklist) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: blacklist,
possible: [_.isString],
})
if (!validOptions) {
return
}
root.walkComments(comment => {
const text = comment.text
const rawComment = comment.toString()
const firstFourChars = rawComment.substr(0, 4)
// Return early if sourcemap
if (firstFourChars === "/*# ") {
return
}
const matchesWord = matchesStringOrRegExp(text, blacklist) || containsString(text, blacklist)
if (!matchesWord) {
return
}
report({
message: messages.rejected(matchesWord.pattern),
node: comment,
result,
ruleName,
})
})
}
}
rule.primaryOptionArray = true
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,33 @@
# custom-media-pattern
Specify a pattern for custom media query names.
```css
@custom-media --foo (max-width: 30em);
/** ↑
* The pattern of this */
```
## Options
`regex|string`
A string will be translated into a RegExp like so `new RegExp(yourString)` so be sure to escape properly.
Given the string:
```js
"foo-.+"
```
The following patterns are considered warnings:
```css
@custom-media --bar (min-width: 30em);
```
The following patterns are *not* considered warnings:
```css
@custom-media --foo-bar (min-width: 30em);
```

View File

@@ -0,0 +1,54 @@
"use strict"
const _ = require("lodash")
const atRuleParamIndex = require("../../utils/atRuleParamIndex")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "custom-media-pattern"
const messages = ruleMessages(ruleName, {
expected: "Expected custom media query name to match specified pattern",
})
const rule = function (pattern) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: pattern,
possible: [
_.isRegExp,
_.isString,
],
})
if (!validOptions) {
return
}
const regexpPattern = _.isString(pattern) ? new RegExp(pattern) : pattern
root.walkAtRules(atRule => {
if (atRule.name.toLowerCase() !== "custom-media") {
return
}
const customMediaName = atRule.params.match(/^--(\S+)\b/)[1]
if (regexpPattern.test(customMediaName)) {
return
}
report({
message: messages.expected,
node: atRule,
index: atRuleParamIndex(atRule),
result,
ruleName,
})
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,197 @@
# custom-property-empty-line-before
Require or disallow an empty line before custom properties.
```css
a {
top: 10px;
/* ← */
--foo: pink; /* ↑ */
} /* ↑ */
/** ↑
* This line */
```
## Options
`string`: `"always"|"never"`
### `"always"`
The following patterns are considered warnings:
```css
a {
top: 10px;
--foo: pink;
--bar: red;
}
```
The following patterns are *not* considered warnings:
```css
a {
top: 10px;
--foo: pink;
--bar: red;
}
```
### `"never"`
The following patterns are considered warnings:
```css
a {
top: 10px;
--foo: pink;
--bar: red;
}
```
```css
a {
--foo: pink;
--bar: red;
}
```
The following patterns are *not* considered warnings:
```css
a {
top: 10px;
--foo: pink;
--bar: red;
}
```
```css
a {
--foo: pink;
--bar: red;
}
```
## Optional secondary options
### `except: ["after-comment", "after-custom-property", "first-nested"]`
#### `"after-comment"`
Reverse the primary option for custom properties that come after a comment.
For example, with `"always"`:
The following patterns are considered warnings:
```css
a {
--foo: pink;
/* comment */
--bar: red;
}
```
The following patterns are *not* considered warnings:
```css
a {
--foo: pink;
/* comment */
--bar: red;
}
```
#### `"after-custom-property"`
Reverse the primary option for custom properties that come after another custom property.
For example, with `"always"`:
The following patterns are considered warnings:
```css
a {
--foo: pink;
--bar: red;
}
```
The following patterns are *not* considered warnings:
```css
a {
--foo: pink;
--bar: red;
}
```
#### `"first-nested"`
Reverse the primary option for custom properties that are nested and the first child of their parent node.
For example, with `"always"`:
The following patterns are considered warnings:
```css
a {
--foo: pink;
--bar: red;
}
```
The following patterns are *not* considered warnings:
```css
a {
--foo: pink;
--bar: red;
}
```
### `ignore: ["after-comment", "inside-single-line-block"]`
#### `"after-comment"`
Ignore custom properties that are preceded by comments.
For example, with `"always"`:
The following patterns are *not* considered warnings:
```css
a {
/* comment */
--foo: pink;
}
```
#### `"inside-single-line-block"`
Ignore custom properties that are inside single-line blocks.
For example, with `"always"`:
The following patterns are *not* considered warnings:
```css
a { --foo: pink; --bar: red; }
```

View File

@@ -0,0 +1,124 @@
"use strict"
const blockString = require("../../utils/blockString")
const hasEmptyLine = require("../../utils/hasEmptyLine")
const isCustomProperty = require("../../utils/isCustomProperty")
const isSingleLineString = require("../../utils/isSingleLineString")
const isStandardSyntaxDeclaration = require("../../utils/isStandardSyntaxDeclaration")
const optionsMatches = require("../../utils/optionsMatches")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "custom-property-empty-line-before"
const messages = ruleMessages(ruleName, {
expected: "Expected empty line before custom property",
rejected: "Unexpected empty line before custom property",
})
const rule = function (expectation, options) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"always",
"never",
],
}, {
actual: options,
possible: {
except: [
"first-nested",
"after-comment",
"after-custom-property",
],
ignore: [
"after-comment",
"inside-single-line-block",
],
},
optional: true,
})
if (!validOptions) {
return
}
root.walkDecls(decl => {
const prop = decl.prop,
parent = decl.parent
if (!isStandardSyntaxDeclaration(decl)) {
return
}
if (!isCustomProperty(prop)) {
return
}
// Optionally ignore the node if a comment precedes it
if (
optionsMatches(options, "ignore", "after-comment")
&& decl.prev()
&& decl.prev().type === "comment"
) {
return
}
// Optionally ignore nodes inside single-line blocks
if (
optionsMatches(options, "ignore", "inside-single-line-block")
&& isSingleLineString(blockString(parent))
) {
return
}
let expectEmptyLineBefore = expectation === "always" ? true : false
// Optionally reverse the expectation for the first nested node
if (
optionsMatches(options, "except", "first-nested")
&& decl === parent.first
) {
expectEmptyLineBefore = !expectEmptyLineBefore
}
// Optionally reverse the expectation if a comment precedes this node
if (
optionsMatches(options, "except", "after-comment")
&& decl.prev()
&& decl.prev().type === "comment"
) {
expectEmptyLineBefore = !expectEmptyLineBefore
}
// Optionally reverse the expectation if a custom property precedes this node
if (
optionsMatches(options, "except", "after-custom-property")
&& decl.prev()
&& decl.prev().prop
&& isCustomProperty(decl.prev().prop)
) {
expectEmptyLineBefore = !expectEmptyLineBefore
}
const hasEmptyLineBefore = hasEmptyLine(decl.raws.before)
// Return if the expectation is met
if (expectEmptyLineBefore === hasEmptyLineBefore) {
return
}
const message = expectEmptyLineBefore ? messages.expected : messages.rejected
report({
message,
node: decl,
result,
ruleName,
})
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,31 @@
# custom-property-no-outside-root
***Deprecated: See [CHANGELOG](../../../CHANGELOG.md).***
Disallow custom properties outside of `:root` rules.
```css
a { --foo: 1px; }
/** ↑ ↑
* These selectors and these types of custom properties */
```
## Options
### `true`
The following patterns are considered warnings:
```css
a { --foo: 1px; }
```
```css
:root, a { --foo: 1px; }
```
The following patterns are *not* considered warnings:
```css
:root { --foo: 1px; }
```

View File

@@ -0,0 +1,51 @@
"use strict"
const isCustomProperty = require("../../utils/isCustomProperty")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "custom-property-no-outside-root"
const messages = ruleMessages(ruleName, {
rejected: "Unexpected custom property",
})
const rule = function (actual) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual })
if (!validOptions) {
return
}
result.warn((
`'${ruleName}' has been deprecated and in 8.0 will be removed.`
), {
stylelintType: "deprecation",
stylelintReference: `https://stylelint.io/user-guide/rules/${ruleName}/`,
})
root.walkRules(rule => {
// Ignore rules whose selector is just `:root`
if (rule.selector.toLowerCase().trim() === ":root") {
return
}
rule.walkDecls(decl => {
if (!isCustomProperty(decl.prop)) {
return
}
report({
message: messages.rejected,
node: decl,
result,
ruleName,
})
})
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,33 @@
# custom-property-pattern
Specify a pattern for custom properties.
```css
a { --foo-: 1px; }
/** ↑
* The pattern of this */
```
## Options
`regex|string`
A string will be translated into a RegExp like so `new RegExp(yourString)` so be sure to escape properly.
Given the string:
```js
"foo-.+"
```
The following patterns are considered warnings:
```css
:root { --boo-bar: 0; }
```
The following patterns are *not* considered warnings:
```css
:root { --foo-bar: 0; }
```

View File

@@ -0,0 +1,52 @@
"use strict"
const _ = require("lodash")
const isCustomProperty = require("../../utils/isCustomProperty")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "custom-property-pattern"
const messages = ruleMessages(ruleName, {
expected: "Expected custom property name to match specified pattern",
})
const rule = function (pattern) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: pattern,
possible: [
_.isRegExp,
_.isString,
],
})
if (!validOptions) {
return
}
const regexpPattern = _.isString(pattern) ? new RegExp(pattern) : pattern
root.walkDecls(decl => {
const prop = decl.prop
if (!isCustomProperty(prop)) {
return
}
if (regexpPattern.test(prop.slice(2))) {
return
}
report({
message: messages.expected,
node: decl,
result,
ruleName,
})
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,61 @@
# declaration-bang-space-after
Require a single space or disallow whitespace after the bang of declarations.
```css
a { color: pink !important; }
/** ↑
* The space after this exclamation mark */
```
## Options
`string`: `"always"|"never"`
### `"always"`
There *must always* be a single space after the bang.
The following patterns are considered warnings:
```css
a { color: pink !important; }
```
```css
a { color: pink !important; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink ! important; }
```
```css
a { color: pink! important; }
```
### `"never"`
There *must never* be whitespace after the bang.
The following patterns are considered warnings:
```css
a { color: pink ! important; }
```
```css
a { color: pink! important; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink !important; }
```
```css
a { color:pink!important; }
```

View File

@@ -0,0 +1,40 @@
"use strict"
const declarationBangSpaceChecker = require("../declarationBangSpaceChecker")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const whitespaceChecker = require("../../utils/whitespaceChecker")
const ruleName = "declaration-bang-space-after"
const messages = ruleMessages(ruleName, {
expectedAfter: () => "Expected single space after \"!\"",
rejectedAfter: () => "Unexpected whitespace after \"!\"",
})
const rule = function (expectation) {
const checker = whitespaceChecker("space", expectation, messages)
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"always",
"never",
],
})
if (!validOptions) {
return
}
declarationBangSpaceChecker({
root,
result,
locationChecker: checker.after,
checkedRuleName: ruleName,
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,57 @@
# declaration-bang-space-before
Require a single space or disallow whitespace before the bang of declarations.
```css
a { color: pink !important; }
/** ↑
* The space before this exclamation mark */
```
## Options
`string`: `"always"|"never"`
### `"always"`
There *must always* be a single space before the bang.
The following patterns are considered warnings:
```css
a { color: pink!important; }
```
```css
a { color: pink ! important; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink !important; }
```
```css
a { color:pink ! important; }
```
### `"never"`
There *must never* be whitespace before the bang.
The following patterns are considered warnings:
```css
a { color : pink !important; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink!important; }
```
```css
a { color: pink! important; }
```

View File

@@ -0,0 +1,40 @@
"use strict"
const declarationBangSpaceChecker = require("../declarationBangSpaceChecker")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const whitespaceChecker = require("../../utils/whitespaceChecker")
const ruleName = "declaration-bang-space-before"
const messages = ruleMessages(ruleName, {
expectedBefore: () => "Expected single space before \"!\"",
rejectedBefore: () => "Unexpected whitespace before \"!\"",
})
const rule = function (expectation) {
const checker = whitespaceChecker("space", expectation, messages)
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [
"always",
"never",
],
})
if (!validOptions) {
return
}
declarationBangSpaceChecker({
root,
result,
locationChecker: checker.before,
checkedRuleName: ruleName,
})
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,129 @@
# declaration-block-no-duplicate-properties
Disallow duplicate properties within declaration blocks.
```css
a { color: pink; color: orange; }
/** ↑ ↑
* These duplicated properties */
```
This rule ignores variables (`$sass`, `@less`, `--custom-property`).
## Options
### `true`
The following patterns are considered warnings:
```css
a { color: pink; color: orange; }
```
```css
a { color: pink; background: orange; color: orange }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; }
```
```css
a { color: pink; background: orange; }
```
## Optional secondary options
### `ignore: ["consecutive-duplicates"]`
Ignore consecutive duplicated properties.
They can prove to be useful fallbacks for older browsers.
The following patterns are considered warnings:
```css
p {
font-size: 16px;
font-weight: 400;
font-size: 1rem;
}
```
The following patterns are *not* considered warnings:
```css
p {
font-size: 16px;
font-size: 1rem;
font-weight: 400;
}
```
### `ignore: ["consecutive-duplicates-with-different-values"]`
Ignore consecutive duplicated properties with different values.
Including duplicate properties (fallbacks) is useful to deal with older browsers support for CSS properties. E.g. using 'px' units when 'rem' isn't available.
The following patterns are considered warnings:
```css
/* properties with the same value */
p {
font-size: 16px;
font-size: 16px;
font-weight: 400;
}
```
```css
/* nonconsecutive duplicates */
p {
font-size: 16px;
font-weight: 400;
font-size: 1rem;
}
```
The following patterns are *not* considered warnings:
```css
p {
font-size: 16px;
font-size: 1rem;
font-weight: 400;
}
```
### `ignoreProperties: ["/regex/", "non-regex"]`
Ignore duplicates of specific properties.
Given:
```js
["color", "/background\-/"]
```
The following patterns are considered warnings:
```css
a { color: pink; background: orange; background: white; }
```
```css
a { background: orange; color: pink; background: white; }
```
The following patterns are *not* considered warnings:
```css
a { color: pink; color: orange; background-color: orange; background-color: white; }
```
```css
a { color: pink; background-color: orange; color: orange; background-color: white; }
```

View File

@@ -0,0 +1,135 @@
"use strict"
const _ = require("lodash")
const isCustomProperty = require("../../utils/isCustomProperty")
const isStandardSyntaxProperty = require("../../utils/isStandardSyntaxProperty")
const optionsMatches = require("../../utils/optionsMatches")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const ruleName = "declaration-block-no-duplicate-properties"
const messages = ruleMessages(ruleName, {
rejected: property => `Unexpected duplicate "${property}"`,
})
const rule = function (on, options) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual: on }, {
actual: options,
possible: {
ignore: [
"consecutive-duplicates",
"consecutive-duplicates-with-different-values",
],
ignoreProperties: [_.isString],
},
optional: true,
})
if (!validOptions) {
return
}
// In order to accommodate nested blocks (postcss-nested),
// we need to run a shallow loop (instead of eachDecl() or eachRule(),
// which loop recursively) and allow each nested block to accumulate
// its own list of properties -- so that a property in a nested rule
// does not conflict with the same property in the parent rule
root.each(node => {
if (
node.type === "rule"
|| node.type === "atrule"
) {
checkRulesInNode(node)
}
})
function checkRulesInNode(node) {
const decls = []
const values = []
node.each(child => {
if (
child.nodes
&& child.nodes.length
) {
checkRulesInNode(child)
}
if (child.type !== "decl") {
return
}
const prop = child.prop
const value = child.value
if (!isStandardSyntaxProperty(prop)) {
return
}
if (isCustomProperty(prop)) {
return
}
// Return early if the property is to be ignored
if (optionsMatches(options, "ignoreProperties", prop)) {
return
}
// Ignore the src property as commonly duplicated in at-fontface
if (prop.toLowerCase() === "src") {
return
}
const indexDuplicate = decls.indexOf(prop.toLowerCase())
if (indexDuplicate !== -1) {
if (optionsMatches(options, "ignore", "consecutive-duplicates-with-different-values")) {
// if duplicates are not consecutive
if (indexDuplicate !== decls.length - 1) {
report({
message: messages.rejected(prop),
node: child,
result,
ruleName,
})
return
}
// if values of consecutive duplicates are equal
if (value === values[indexDuplicate]) {
report({
message: messages.rejected(value),
node: child,
result,
ruleName,
})
return
}
return
}
if (
optionsMatches(options, "ignore", "consecutive-duplicates")
&& indexDuplicate === decls.length - 1
) {
return
}
report({
message: messages.rejected(prop),
node: child,
result,
ruleName,
})
}
decls.push(prop.toLowerCase())
values.push(value.toLowerCase())
})
}
}
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule

View File

@@ -0,0 +1,109 @@
# declaration-block-no-ignored-properties
***Deprecated: See [CHANGELOG](../../../CHANGELOG.md).***
Disallow property values that are ignored due to another property value in the same rule.
```css
a { display: inline; width: 100px; }
/** ↑
* This property */
```
Certain property value pairs rule out other property value pairs, causing them to be ignored by the browser. For example, when an element has display: inline, any further declarations about width, height and margin-top properties will be ignored. Sometimes this is confusing: maybe you forgot that your margin-top will have no effect because the element has display: inline, so you spend a while struggling to figure out what you've done wrong. This rule protects against that confusion by ensuring that within a single rule you don't use property values that are ruled out by other property values in that same rule.
The rule warns when it finds:
- `display: inline` used with `width`, `height`, `margin`, `margin-top`, `margin-bottom`, `overflow` (and all variants).
- `display: list-item` used with `vertical-align`.
- `display: block` used with `vertical-align`.
- `display: flex` used with `vertical-align`.
- `display: table` used with `vertical-align`.
- `display: table-*` used with `margin` (and all variants).
- `display: table-*` (except `table-cell`) used with `vertical-align`.
- `display: table-(row|row-group)` used with `width`, `min-width` or `max-width`.
- `display: table-(column|column-group)` used with `height`, `min-height` or `max-height`.
- `float: left` and `float: right` used with `vertical-align`.
- `position: static` used with `top`, `right`, `bottom`, or `left`.
- `position: absolute` used with `float`, `clear` or `vertical-align`.
- `position: fixed` used with `float`, `clear` or `vertical-align`.
## Options
### `true`
The following patterns are considered warnings:
```css
a { display: inline; width: 100px; }
```
`display: inline` causes `width` to be ignored.
```css
a { display: inline; height: 100px; }
```
`display: inline` causes `height` to be ignored.
```css
a { display: inline; margin: 10px; }
```
`display: inline` causes `margin` to be ignored.
```css
a { display: block; vertical-align: baseline; }
```
`display: block` causes `vertical-align` to be ignored.
```css
a { display: flex; vertical-align: baseline; }
```
`display: flex` causes `vertical-align` to be ignored.
```css
a { position: absolute; vertical-align: baseline; }
```
`position: absolute` causes `vertical-align` to be ignored.
```css
a { float: left; vertical-align: baseline; }
```
`float: left` causes `vertical-align` to be ignored.
The following patterns are *not* considered warnings:
```css
a { display: inline: margin-left: 10px; }
```
```css
a { display: inline: margin-right: 10px; }
```
```css
a { display: inline: padding: 10px; }
```
```css
a { display: inline-block; width: 100px; }
```
Although `display: inline` causes `width` to be ignored, `inline-block` works with `width`.
```css
a { display: table-cell; vertical-align: baseline; }
```
Although `display: block` causes `vertical-align` to be ignored, `table-cell` works with `vertical-align`.
```css
a { position: relative; vertical-align: baseline; }
```
Although `position: absolute` causes `vertical-align` to be ignored, `relative` works with `vertical-align`.

Some files were not shown because too many files have changed in this diff Show More