added all files to project

This commit is contained in:
2022-03-10 10:36:59 +01:00
parent 09dd957b33
commit 46a936d7de
25351 changed files with 3883356 additions and 0 deletions
+407
View File
@@ -0,0 +1,407 @@
'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';
/** @typedef {import('postcss/lib/comment')} PostcssComment */
/** @typedef {import('postcss').Root} PostcssRoot */
/** @typedef {import('stylelint').PostcssResult} PostcssResult */
/** @typedef {import('stylelint').DisabledRangeObject} DisabledRangeObject */
/** @typedef {import('stylelint').DisabledRange} DisabledRange */
/**
* @param {PostcssComment} comment
* @param {number} start
* @param {boolean} strictStart
* @param {string|undefined} description
* @param {number} [end]
* @param {boolean} [strictEnd]
* @returns {DisabledRange}
*/
function createDisableRange(comment, start, strictStart, description, end, strictEnd) {
return {
comment,
start,
end: end || undefined,
strictStart,
strictEnd: typeof strictEnd === 'boolean' ? strictEnd : undefined,
description,
};
}
/**
* Run it like a plugin ...
* @param {PostcssRoot} root
* @param {PostcssResult} result
* @returns {PostcssResult}
*/
module.exports = function (root, result) {
result.stylelint = result.stylelint || {
disabledRanges: {},
ruleSeverities: {},
customMessages: {},
};
/**
* Most of the functions below work via side effects mutating this object
* @type {DisabledRangeObject}
*/
const disabledRanges = {
all: [],
};
result.stylelint.disabledRanges = disabledRanges;
// Work around postcss/postcss-scss#109 by merging adjacent `//` comments
// into a single node before passing to `checkComment`.
/** @type {PostcssComment?} */
let inlineEnd;
root.walkComments((/** @type {PostcssComment} */ comment) => {
if (inlineEnd) {
// Ignore comments already processed by grouping with a previous one.
if (inlineEnd === comment) inlineEnd = null;
return;
}
const nextComment = comment.next();
// If any of these conditions are not met, do not merge comments.
if (
!(
isInlineComment(comment) &&
isStylelintCommand(comment) &&
nextComment &&
nextComment.type === 'comment' &&
(comment.text.includes('--') || nextComment.text.startsWith('--'))
)
) {
checkComment(comment);
return;
}
let lastLine = (comment.source && comment.source.end && comment.source.end.line) || 0;
const fullComment = comment.clone();
let current = nextComment;
while (isInlineComment(current) && !isStylelintCommand(current)) {
const currentLine = (current.source && current.source.end && current.source.end.line) || 0;
if (lastLine + 1 !== currentLine) break;
fullComment.text += `\n${current.text}`;
if (fullComment.source && current.source) {
fullComment.source.end = current.source.end;
}
inlineEnd = current;
const next = current.next();
if (!next || next.type !== 'comment') break;
current = next;
lastLine = currentLine;
}
checkComment(fullComment);
});
return result;
/**
* @param {PostcssComment} comment
*/
function isInlineComment(comment) {
// We check both here because the Sass parser uses `raws.inline` to indicate
// inline comments, while the Less parser uses `inline`.
return comment.inline || comment.raws.inline;
}
/**
* @param {PostcssComment} comment
*/
function isStylelintCommand(comment) {
return comment.text.startsWith(disableCommand) || comment.text.startsWith(enableCommand);
}
/**
* @param {PostcssComment} comment
*/
function processDisableLineCommand(comment) {
if (comment.source && comment.source.start) {
const line = comment.source.start.line;
const description = getDescription(comment.text);
getCommandRules(disableLineCommand, comment.text).forEach((ruleName) => {
disableLine(comment, line, ruleName, description);
});
}
}
/**
* @param {PostcssComment} comment
*/
function processDisableNextLineCommand(comment) {
if (comment.source && comment.source.end) {
const line = comment.source.end.line;
const description = getDescription(comment.text);
getCommandRules(disableNextLineCommand, comment.text).forEach((ruleName) => {
disableLine(comment, line + 1, ruleName, description);
});
}
}
/**
* @param {PostcssComment} comment
* @param {number} line
* @param {string} ruleName
* @param {string|undefined} description
*/
function disableLine(comment, line, ruleName, description) {
if (ruleIsDisabled(ALL_RULES)) {
throw comment.error('All rules have already been disabled', {
plugin: 'stylelint',
});
}
if (ruleName === ALL_RULES) {
Object.keys(disabledRanges).forEach((disabledRuleName) => {
if (ruleIsDisabled(disabledRuleName)) return;
const strict = disabledRuleName === ALL_RULES;
startDisabledRange(comment, line, disabledRuleName, strict, description);
endDisabledRange(line, disabledRuleName, strict);
});
} else {
if (ruleIsDisabled(ruleName)) {
throw comment.error(`"${ruleName}" has already been disabled`, {
plugin: 'stylelint',
});
}
startDisabledRange(comment, line, ruleName, true, description);
endDisabledRange(line, ruleName, true);
}
}
/**
* @param {PostcssComment} comment
*/
function processDisableCommand(comment) {
const description = getDescription(comment.text);
getCommandRules(disableCommand, comment.text).forEach((ruleToDisable) => {
const isAllRules = ruleToDisable === ALL_RULES;
if (ruleIsDisabled(ruleToDisable)) {
throw comment.error(
isAllRules
? 'All rules have already been disabled'
: `"${ruleToDisable}" has already been disabled`,
{
plugin: 'stylelint',
},
);
}
if (comment.source && comment.source.start) {
const line = comment.source.start.line;
if (isAllRules) {
Object.keys(disabledRanges).forEach((ruleName) => {
startDisabledRange(comment, line, ruleName, ruleName === ALL_RULES, description);
});
} else {
startDisabledRange(comment, line, ruleToDisable, true, description);
}
}
});
}
/**
* @param {PostcssComment} comment
*/
function processEnableCommand(comment) {
getCommandRules(enableCommand, comment.text).forEach((ruleToEnable) => {
// TODO TYPES
// need fallback if endLine will be undefined
const endLine = /** @type {number} */ (comment.source &&
comment.source.end &&
comment.source.end.line);
if (ruleToEnable === ALL_RULES) {
if (
Object.values(disabledRanges).every(
(ranges) => ranges.length === 0 || typeof ranges[ranges.length - 1].end === 'number',
)
) {
throw comment.error('No rules have been disabled', {
plugin: 'stylelint',
});
}
Object.keys(disabledRanges).forEach((ruleName) => {
if (!_.get(_.last(disabledRanges[ruleName]), 'end')) {
endDisabledRange(endLine, ruleName, ruleName === ALL_RULES);
}
});
return;
}
if (ruleIsDisabled(ALL_RULES) && disabledRanges[ruleToEnable] === undefined) {
// Get a starting point from the where all rules were disabled
if (!disabledRanges[ruleToEnable]) {
disabledRanges[ruleToEnable] = disabledRanges.all.map(({ start, end, description }) =>
createDisableRange(comment, start, false, description, end, false),
);
} else {
const range = _.last(disabledRanges[ALL_RULES]);
if (range) {
disabledRanges[ruleToEnable].push({ ...range });
}
}
endDisabledRange(endLine, ruleToEnable, true);
return;
}
if (ruleIsDisabled(ruleToEnable)) {
endDisabledRange(endLine, ruleToEnable, true);
return;
}
throw comment.error(`"${ruleToEnable}" has not been disabled`, {
plugin: 'stylelint',
});
});
}
/**
* @param {PostcssComment} comment
*/
function checkComment(comment) {
const text = comment.text;
// Ignore comments that are not relevant commands
if (text.indexOf(COMMAND_PREFIX) !== 0) {
return result;
}
if (text.startsWith(disableLineCommand)) {
processDisableLineCommand(comment);
} else if (text.startsWith(disableNextLineCommand)) {
processDisableNextLineCommand(comment);
} else if (text.startsWith(disableCommand)) {
processDisableCommand(comment);
} else if (text.startsWith(enableCommand)) {
processEnableCommand(comment);
}
}
/**
* @param {string} command
* @param {string} fullText
* @returns {string[]}
*/
function getCommandRules(command, fullText) {
const rules = fullText
.slice(command.length)
.split(/\s-{2,}\s/u)[0] // Allow for description (f.e. /* stylelint-disable a, b -- Description */).
.trim()
.split(',')
.filter(Boolean)
.map((r) => r.trim());
if (_.isEmpty(rules)) {
return [ALL_RULES];
}
return rules;
}
/**
* @param {string} fullText
* @returns {string|undefined}
*/
function getDescription(fullText) {
const descriptionStart = fullText.indexOf('--');
if (descriptionStart === -1) return;
return fullText.slice(descriptionStart + 2).trim();
}
/**
* @param {PostcssComment} comment
* @param {number} line
* @param {string} ruleName
* @param {boolean} strict
* @param {string|undefined} description
*/
function startDisabledRange(comment, line, ruleName, strict, description) {
const rangeObj = createDisableRange(comment, line, strict, description);
ensureRuleRanges(ruleName);
disabledRanges[ruleName].push(rangeObj);
}
/**
* @param {number} line
* @param {string} ruleName
* @param {boolean} strict
*/
function endDisabledRange(line, ruleName, strict) {
const lastRangeForRule = _.last(disabledRanges[ruleName]);
if (!lastRangeForRule) {
return;
}
// Add an `end` prop to the last range of that rule
lastRangeForRule.end = line;
lastRangeForRule.strictEnd = strict;
}
/**
* @param {string} ruleName
*/
function ensureRuleRanges(ruleName) {
if (!disabledRanges[ruleName]) {
disabledRanges[ruleName] = disabledRanges.all.map(({ comment, start, end, description }) =>
createDisableRange(comment, start, false, description, end, false),
);
}
}
/**
* @param {string} ruleName
* @returns {boolean}
*/
function ruleIsDisabled(ruleName) {
if (disabledRanges[ruleName] === undefined) return false;
if (_.last(disabledRanges[ruleName]) === undefined) return false;
if (_.get(_.last(disabledRanges[ruleName]), 'end') === undefined) return true;
return false;
}
};
+359
View File
@@ -0,0 +1,359 @@
'use strict';
const _ = require('lodash');
const configurationError = require('./utils/configurationError');
const getModulePath = require('./utils/getModulePath');
const globjoin = require('globjoin');
const normalizeAllRuleSettings = require('./normalizeAllRuleSettings');
const path = require('path');
/** @typedef {import('stylelint').StylelintConfigPlugins} StylelintConfigPlugins */
/** @typedef {import('stylelint').StylelintConfigProcessor} StylelintConfigProcessor */
/** @typedef {import('stylelint').StylelintConfigProcessors} StylelintConfigProcessors */
/** @typedef {import('stylelint').StylelintConfigRules} StylelintConfigRules */
/** @typedef {import('stylelint').StylelintInternalApi} StylelintInternalApi */
/** @typedef {import('stylelint').StylelintConfig} StylelintConfig */
/** @typedef {import('stylelint').CosmiconfigResult} CosmiconfigResult */
/**
* - Merges config and configOverrides
* - Makes all paths absolute
* - Merges extends
* @param {StylelintInternalApi} stylelint
* @param {StylelintConfig} config
* @param {string} configDir
* @param {boolean} [allowOverrides]
* @returns {Promise<StylelintConfig>}
*/
function augmentConfigBasic(stylelint, config, configDir, allowOverrides) {
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.
* @param {StylelintInternalApi} stylelint
* @param {CosmiconfigResult} [cosmiconfigResult]
* @returns {Promise<CosmiconfigResult | null>}
*/
function augmentConfigExtended(stylelint, cosmiconfigResult) {
if (!cosmiconfigResult) return Promise.resolve(null);
const configDir = path.dirname(cosmiconfigResult.filepath || '');
const { ignoreFiles, ...cleanedConfig } = cosmiconfigResult.config;
return augmentConfigBasic(stylelint, cleanedConfig, configDir).then((augmentedConfig) => {
return {
config: augmentedConfig,
filepath: cosmiconfigResult.filepath,
};
});
}
/**
* @param {StylelintInternalApi} stylelint
* @param {CosmiconfigResult} [cosmiconfigResult]
* @returns {Promise<CosmiconfigResult | null>}
*/
function augmentConfigFull(stylelint, cosmiconfigResult) {
if (!cosmiconfigResult) return Promise.resolve(null);
const config = cosmiconfigResult.config;
const filepath = cosmiconfigResult.filepath;
const configDir = stylelint._options.configBasedir || path.dirname(filepath || '');
return augmentConfigBasic(stylelint, config, configDir, true)
.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,
};
});
}
/**
* Make all paths in the config absolute:
* - ignoreFiles
* - plugins
* - processors
* (extends handled elsewhere)
* @param {StylelintConfig} config
* @param {string} configDir
* @returns {StylelintConfig}
*/
function absolutizePaths(config, configDir) {
if (config.ignoreFiles) {
config.ignoreFiles = /** @type {string[]} */ ([]).concat(config.ignoreFiles).map((glob) => {
if (path.isAbsolute(glob.replace(/^!/, ''))) return glob;
return globjoin(configDir, glob);
});
}
if (config.plugins) {
config.plugins = /** @type {string[]} */ ([]).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
* @param {StylelintConfigProcessors} processors
* @param {string} configDir
* @return {StylelintConfigProcessors}
*/
function absolutizeProcessors(processors, configDir) {
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]];
});
}
/**
* @param {StylelintInternalApi} stylelint
* @param {StylelintConfig} config
* @param {string} configDir
* @return {Promise<StylelintConfig>}
*/
function extendConfig(stylelint, config, configDir) {
if (config.extends === undefined) return Promise.resolve(config);
const normalizedExtends = Array.isArray(config.extends) ? config.extends : [config.extends];
const { extends: configExtends, ...originalWithoutExtends } = config;
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);
});
}
/**
* @param {StylelintInternalApi} stylelint
* @param {StylelintConfig} config
* @param {string} configDir
* @param {string} extendLookup
* @return {Promise<CosmiconfigResult | null>}
*/
function loadExtendedConfig(stylelint, config, configDir, extendLookup) {
const extendPath = getModulePath(configDir, extendLookup);
return stylelint._extendExplorer.load(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
* @param {StylelintConfig} a
* @param {StylelintConfig} b
* @returns {StylelintConfig}
*/
function mergeConfigs(a, b) {
/** @type {{plugins: StylelintConfigPlugins}} */
const pluginMerger = {};
if (a.plugins || b.plugins) {
pluginMerger.plugins = [];
if (a.plugins) {
pluginMerger.plugins = pluginMerger.plugins.concat(a.plugins);
}
if (b.plugins) {
pluginMerger.plugins = [...new Set(pluginMerger.plugins.concat(b.plugins))];
}
}
/** @type {{processors: StylelintConfigProcessors}} */
const processorMerger = {};
if (a.processors || b.processors) {
processorMerger.processors = [];
if (a.processors) {
processorMerger.processors = processorMerger.processors.concat(a.processors);
}
if (b.processors) {
processorMerger.processors = [...new Set(processorMerger.processors.concat(b.processors))];
}
}
const rulesMerger = {};
if (a.rules || b.rules) {
rulesMerger.rules = { ...a.rules, ...b.rules };
}
const result = { ...a, ...b, ...processorMerger, ...pluginMerger, ...rulesMerger };
return result;
}
/**
* @param {StylelintConfig} config
* @returns {StylelintConfig}
*/
function addPluginFunctions(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 (!pluginRuleDefinition.ruleName.includes('/')) {
throw configurationError(
'stylelint v7+ requires plugin rules to be namespaced, ' +
'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;
}, /** @type {{[k: string]: Function}} */ ({}));
config.pluginFunctions = pluginFunctions;
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
* - Initialize the processor module by calling its functions with any
* provided options
* - Push the processor's code and result processors to their respective arrays
* @type {Map<string, string | Object>}
*/
const processorCache = new Map();
/**
* @param {StylelintConfig} config
* @return {StylelintConfig}
*/
function addProcessorFunctions(config) {
if (!config.processors) return config;
/** @type {Array<Function>} */
const codeProcessors = [];
/** @type {Array<Function>} */
const resultProcessors = [];
/** @type {Array<StylelintConfigProcessor>} */ ([])
.concat(config.processors)
.forEach((processorConfig) => {
const processorKey = JSON.stringify(processorConfig);
let initializedProcessor;
if (processorCache.has(processorKey)) {
initializedProcessor = processorCache.get(processorKey);
} else {
const processorLookup =
typeof processorConfig === 'string' ? processorConfig : processorConfig[0];
const processorOptions =
typeof processorConfig === 'string' ? undefined : 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 };
+544
View File
@@ -0,0 +1,544 @@
'use strict';
const chalk = require('chalk');
const checkInvalidCLIOptions = require('./utils/checkInvalidCLIOptions');
const EOL = require('os').EOL;
const getFormatterOptionsText = require('./utils/getFormatterOptionsText');
const getModulePath = require('./utils/getModulePath');
const getStdin = require('get-stdin');
const meow = require('meow');
const path = require('path');
const printConfig = require('./printConfig');
const resolveFrom = require('resolve-from');
const standalone = require('./standalone');
const writeOutputFile = require('./writeOutputFile');
const EXIT_CODE_ERROR = 2;
/**
* @typedef {object} CLIFlags
* @property {boolean} [cache]
* @property {string} [cacheLocation]
* @property {string | false} config
* @property {string} [configBasedir]
* @property {string} [customSyntax]
* @property {string} [printConfig]
* @property {string} [color]
* @property {string} [customFormatter]
* @property {boolean} [disableDefaultIgnores]
* @property {boolean} [fix]
* @property {string} [formatter="string"]
* @property {string} [help]
* @property {boolean} [ignoreDisables]
* @property {string} [ignorePath]
* @property {string[]} [ignorePattern]
* @property {string} [noColor]
* @property {string} [outputFile]
* @property {boolean} [stdin]
* @property {string} [stdinFilename]
* @property {boolean} [reportNeedlessDisables]
* @property {boolean} [reportInvalidScopeDisables]
* @property {boolean} [reportDescriptionlessDisables]
* @property {number} [maxWarnings]
* @property {string | boolean} quiet
* @property {string} [syntax]
* @property {string} [version]
* @property {boolean} [allowEmptyInput]
*/
/**
* @typedef {object} CLIOptions
* @property {any} input
* @property {any} help
* @property {any} pkg
* @property {Function} showHelp
* @property {Function} showVersion
* @property {CLIFlags} flags
*/
/**
* @typedef {object} OptionBaseType
* @property {any} formatter
* @property {boolean} [cache]
* @property {string} [configFile]
* @property {string} [cacheLocation]
* @property {string} [customSyntax]
* @property {string} [codeFilename]
* @property {string} [configBasedir]
* @property {{ quiet?: any }} configOverrides
* @property {any} [printConfig]
* @property {boolean} [fix]
* @property {boolean} [ignoreDisables]
* @property {any} [ignorePath]
* @property {string} [outputFile]
* @property {boolean} [reportNeedlessDisables]
* @property {boolean} [reportInvalidScopeDisables]
* @property {boolean} [reportDescriptionlessDisables]
* @property {boolean} [disableDefaultIgnores]
* @property {number} [maxWarnings]
* @property {string} [syntax]
* @property {string[]} [ignorePattern]
* @property {boolean} [allowEmptyInput]
* @property {string} [files]
* @property {string} [code]
*/
const meowOptions = {
autoHelp: false,
autoVersion: false,
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
globby for cross-platform glob support. node_modules 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, .yml, 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.
--print-config
Print the configuration for the given path.
--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().
--ignore-pattern, --ip
Pattern of files to ignore (in addition to those in .stylelintignore)
--syntax, -s
Specify a syntax. Options: "css", "css-in-js", "html", "less",
"markdown", "sass", "scss", "sugarss". If you do not specify a syntax,
syntaxes will be automatically inferred by the file extensions
and file content.
--fix
Automatically fix violations of certain rules.
--custom-syntax
Module name or path to a JS file exporting a PostCSS-compatible syntax.
--stdin
Accept stdin input even if it is empty.
--stdin-filename
A filename to assign stdin input.
--ignore-disables, --id
Ignore styleline-disable comments.
--disable-default-ignores, --di
Allow linting of node_modules.
--cache [default: false]
Store the info about processed files in order to only operate on the
changed ones the next time you run stylelint. By default, the cache
is stored in "./.stylelintcache". To adjust this, use --cache-location.
--cache-location [default: '.stylelintcache']
Path to a file or directory to be used for the cache location.
Default is "./.stylelintcache". If a directory is specified, a cache
file will be created inside the specified folder, with a name derived
from a hash of the current working directory.
If the directory for the cache does not exist, make sure you add a trailing "/"
on *nix systems or "\\" on Windows. Otherwise the path will be assumed to be a file.
--formatter, -f [default: "string"]
The output formatter: ${getFormatterOptionsText({ useOr: true })}.
--custom-formatter
Path to a JS file exporting a custom formatting function.
--quiet, -q
Only register violations for rules with an "error"-level severity (ignore
"warning"-level).
--color
--no-color
Force enabling/disabling of color.
--report-needless-disables, --rd
Also report errors for stylelint-disable comments that are not blocking a lint warning.
The process will exit with code ${EXIT_CODE_ERROR} if needless disables are found.
--report-invalid-scope-disables, --risd
Report stylelint-disable comments that used for rules that don't exist within the configuration object.
The process will exit with code ${EXIT_CODE_ERROR} if invalid scope disables are found.
--report-descriptionless-disables, --rdd
Report stylelint-disable comments without a description.
The process will exit with code ${EXIT_CODE_ERROR} if descriptionless disables are found.
--max-warnings, --mw
Number of warnings above which the process will exit with code ${EXIT_CODE_ERROR}.
Useful when setting "defaultSeverity" to "warning" and expecting the
process to fail on warnings (e.g. CI build).
--output-file, -o
Path of file to write report.
--version, -v
Show the currently installed version of stylelint.
--allow-empty-input, --aei
When glob pattern matches no files, the process will exit without throwing an error.
`,
flags: {
allowEmptyInput: {
alias: 'aei',
type: 'boolean',
},
cache: {
type: 'boolean',
},
cacheLocation: {
type: 'string',
},
color: {
type: 'boolean',
},
config: {
type: 'string',
},
configBasedir: {
type: 'string',
},
customFormatter: {
type: 'string',
},
customSyntax: {
type: 'string',
},
disableDefaultIgnores: {
alias: 'di',
type: 'boolean',
},
fix: {
type: 'boolean',
},
formatter: {
alias: 'f',
default: 'string',
type: 'string',
},
help: {
alias: 'h',
type: 'boolean',
},
ignoreDisables: {
alias: 'id',
type: 'boolean',
},
ignorePath: {
alias: 'i',
type: 'string',
},
ignorePattern: {
alias: 'ip',
type: 'string',
isMultiple: true,
},
maxWarnings: {
alias: 'mw',
type: 'number',
},
outputFile: {
alias: 'o',
type: 'string',
},
printConfig: {
type: 'boolean',
},
quiet: {
alias: 'q',
type: 'boolean',
},
reportDescriptionlessDisables: {
alias: 'rdd',
type: 'boolean',
},
reportInvalidScopeDisables: {
alias: 'risd',
type: 'boolean',
},
reportNeedlessDisables: {
alias: 'rd',
type: 'boolean',
},
stdin: {
type: 'boolean',
},
stdinFilename: {
type: 'string',
},
syntax: {
alias: 's',
type: 'string',
},
version: {
alias: 'v',
type: 'boolean',
},
},
pkg: require('../package.json'),
argv: /** @type {string[]} */ ([]),
};
/**
* @param {string[]} argv
* @returns {Promise<any>}
*/
module.exports = (argv) => {
const cli = buildCLI(argv);
const invalidOptionsMessage = checkInvalidCLIOptions(meowOptions.flags, cli.flags);
if (invalidOptionsMessage) {
process.stderr.write(invalidOptionsMessage);
process.exit(EXIT_CODE_ERROR); // eslint-disable-line no-process-exit
}
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);
}
/** @type {OptionBaseType} */
const optionsBase = {
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.silent(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.ignorePattern) {
optionsBase.ignorePattern = cli.flags.ignorePattern;
}
if (cli.flags.ignoreDisables) {
optionsBase.ignoreDisables = cli.flags.ignoreDisables;
}
if (cli.flags.disableDefaultIgnores) {
optionsBase.disableDefaultIgnores = cli.flags.disableDefaultIgnores;
}
if (cli.flags.cache) {
optionsBase.cache = true;
}
if (cli.flags.cacheLocation) {
optionsBase.cacheLocation = cli.flags.cacheLocation;
}
if (cli.flags.fix) {
optionsBase.fix = cli.flags.fix;
}
if (cli.flags.outputFile) {
optionsBase.outputFile = cli.flags.outputFile;
}
const reportNeedlessDisables = cli.flags.reportNeedlessDisables;
const reportInvalidScopeDisables = cli.flags.reportInvalidScopeDisables;
const reportDescriptionlessDisables = cli.flags.reportDescriptionlessDisables;
if (reportNeedlessDisables) {
optionsBase.reportNeedlessDisables = reportNeedlessDisables;
}
if (reportInvalidScopeDisables) {
optionsBase.reportInvalidScopeDisables = reportInvalidScopeDisables;
}
if (reportDescriptionlessDisables) {
optionsBase.reportDescriptionlessDisables = reportDescriptionlessDisables;
}
const maxWarnings = cli.flags.maxWarnings;
if (maxWarnings !== undefined) {
optionsBase.maxWarnings = maxWarnings;
}
if (cli.flags.help) {
cli.showHelp(0);
return Promise.resolve();
}
if (cli.flags.version) {
cli.showVersion();
return Promise.resolve();
}
if (cli.flags.allowEmptyInput) {
optionsBase.allowEmptyInput = cli.flags.allowEmptyInput;
}
return Promise.resolve()
.then(
/**
* @returns {Promise<OptionBaseType>}
*/
() => {
// Add input/code into options
if (cli.input.length) {
return Promise.resolve({ ...optionsBase, files: /** @type {string} */ (cli.input) });
}
return getStdin().then((stdin) => ({ ...optionsBase, code: stdin }));
},
)
.then((options) => {
if (cli.flags.printConfig) {
return printConfig(options)
.then((config) => {
process.stdout.write(JSON.stringify(config, null, ' '));
})
.catch(handleError);
}
if (!options.files && !options.code && !cli.flags.stdin) {
cli.showHelp();
return;
}
return standalone(options)
.then((linted) => {
if (!linted.output) {
return;
}
process.stdout.write(linted.output);
if (options.outputFile) {
writeOutputFile(linted.output, options.outputFile).catch(handleError);
}
if (linted.errored) {
process.exitCode = EXIT_CODE_ERROR;
} else if (maxWarnings !== undefined && linted.maxWarningsExceeded) {
const foundWarnings = linted.maxWarningsExceeded.foundWarnings;
process.stderr.write(
`${EOL}${chalk.red(`Max warnings exceeded: `)}${foundWarnings} found. ${chalk.dim(
`${maxWarnings} allowed${EOL}${EOL}`,
)}`,
);
process.exitCode = EXIT_CODE_ERROR;
}
})
.catch(handleError);
});
};
/**
* @param {{ stack: any, code: any }} err
* @returns {void}
*/
function handleError(err) {
process.stderr.write(err.stack + EOL);
const exitCode = typeof err.code === 'number' ? err.code : 1;
process.exitCode = exitCode;
}
/**
* @param {string[]} argv
* @returns {CLIOptions}
*/
function buildCLI(argv) {
// @ts-ignore TODO TYPES
return meow({ ...meowOptions, argv });
}
module.exports.buildCLI = buildCLI;
+103
View File
@@ -0,0 +1,103 @@
'use strict';
const _ = require('lodash');
/** @typedef {import('stylelint').PostcssResult} PostcssResult */
/** @typedef {import('postcss').NodeSource} NodeSource */
/** @typedef {import('stylelint').StylelintResult} StylelintResult */
/**
* @param {PostcssResult} [postcssResult]
* @param {import('stylelint').StylelintCssSyntaxError} [cssSyntaxError]
* @return {StylelintResult}
*/
module.exports = function (postcssResult, cssSyntaxError) {
/** @type {StylelintResult} */
let stylelintResult;
/** @type {string | undefined} */
let source;
if (postcssResult && postcssResult.root) {
if (postcssResult.root.source) {
source = postcssResult.root.source.input.file;
if (!source && 'id' in postcssResult.root.source.input) {
source = 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,
};
});
const parseErrors = _.remove(postcssResult.messages, {
stylelintType: 'parseError',
});
// This defines the stylelint result object that formatters receive
stylelintResult = {
source,
deprecations,
invalidOptionWarnings,
// TODO TYPES check which types are valid? postcss? stylelint?
/* eslint-disable-next-line object-shorthand */
parseErrors: /** @type {any} */ (parseErrors),
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,
};
} else if (cssSyntaxError) {
if (cssSyntaxError.name !== 'CssSyntaxError') {
throw cssSyntaxError;
}
stylelintResult = {
source: cssSyntaxError.file || '<input css 1>',
deprecations: [],
invalidOptionWarnings: [],
parseErrors: [],
errored: true,
warnings: [
{
line: cssSyntaxError.line,
column: cssSyntaxError.column,
rule: cssSyntaxError.name,
severity: 'error',
text: `${cssSyntaxError.reason} (${cssSyntaxError.name})`,
},
],
};
} else {
throw new Error(
'createPartialStylelintResult must be called with either postcssResult or CssSyntaxError',
);
}
return stylelintResult;
};
+15
View File
@@ -0,0 +1,15 @@
'use strict';
/** @typedef {import('stylelint').StylelintRule} StylelintRule */
/**
* @param {string} ruleName
* @param {StylelintRule} rule
* @returns {{ruleName: string, rule: StylelintRule}}
*/
module.exports = function (ruleName, rule) {
return {
ruleName,
rule,
};
};
+88
View File
@@ -0,0 +1,88 @@
'use strict';
const _ = require('lodash');
const augmentConfig = require('./augmentConfig');
const createStylelintResult = require('./createStylelintResult');
const getConfigForFile = require('./getConfigForFile');
const getPostcssResult = require('./getPostcssResult');
const isPathIgnored = require('./isPathIgnored');
const lintSource = require('./lintSource');
const path = require('path');
const { cosmiconfig } = require('cosmiconfig');
const IS_TEST = process.env.NODE_ENV === 'test';
const STOP_DIR = IS_TEST ? path.resolve(__dirname, '..') : undefined;
/** @typedef {import('stylelint').StylelintInternalApi} StylelintInternalApi */
/**
* The stylelint "internal API" is passed among functions
* so that methods on a stylelint instance can invoke
* each other while sharing options and caches
* @param {import('stylelint').StylelintStandaloneOptions} options
* @returns {StylelintInternalApi}
*/
module.exports = function (options = {}) {
/** @type {Partial<StylelintInternalApi>} */
const stylelint = { _options: options };
options.configOverrides = options.configOverrides || {};
if (options.ignoreDisables) {
options.configOverrides.ignoreDisables = options.ignoreDisables;
}
if (options.reportNeedlessDisables) {
options.configOverrides.reportNeedlessDisables = options.reportNeedlessDisables;
}
if (options.reportInvalidScopeDisables) {
options.configOverrides.reportInvalidScopeDisables = options.reportInvalidScopeDisables;
}
if (options.reportDescriptionlessDisables) {
options.configOverrides.reportDescriptionlessDisables = options.reportDescriptionlessDisables;
}
// Two separate explorers so they can each have their own transform
// function whose results are cached by cosmiconfig
stylelint._fullExplorer = cosmiconfig('stylelint', {
// @ts-ignore TODO TYPES found out which cosmiconfig types are valid
transform: _.partial(
augmentConfig.augmentConfigFull,
/** @type{StylelintInternalApi} */ (stylelint),
),
stopDir: STOP_DIR,
});
// @ts-ignore TODO TYPES found out which cosmiconfig types are valid
stylelint._extendExplorer = cosmiconfig(null, {
transform: _.partial(
augmentConfig.augmentConfigExtended,
/** @type{StylelintInternalApi} */ (stylelint),
),
stopDir: STOP_DIR,
});
stylelint._specifiedConfigCache = new Map();
stylelint._postcssResultCache = new Map();
stylelint._createStylelintResult = _.partial(
createStylelintResult,
/** @type{StylelintInternalApi} */ (stylelint),
);
stylelint._getPostcssResult = _.partial(
getPostcssResult,
/** @type{StylelintInternalApi} */ (stylelint),
);
stylelint._lintSource = _.partial(lintSource, /** @type{StylelintInternalApi} */ (stylelint));
stylelint.getConfigForFile = _.partial(
getConfigForFile,
/** @type{StylelintInternalApi} */ (stylelint),
);
stylelint.isPathIgnored = _.partial(
isPathIgnored,
/** @type{StylelintInternalApi} */ (stylelint),
);
return /** @type{StylelintInternalApi} */ (stylelint);
};
+39
View File
@@ -0,0 +1,39 @@
'use strict';
const createPartialStylelintResult = require('./createPartialStylelintResult');
/** @typedef {import('stylelint').PostcssResult} PostcssResult */
/** @typedef {import('postcss').NodeSource} NodeSource */
/** @typedef {import('stylelint').StylelintResult} StylelintResult */
/**
* @param {import('stylelint').StylelintInternalApi} stylelint
* @param {PostcssResult} [postcssResult]
* @param {string} [filePath]
* @param {import('stylelint').StylelintCssSyntaxError} [cssSyntaxError]
* @return {Promise<StylelintResult>}
*/
module.exports = function (stylelint, postcssResult, filePath, cssSyntaxError) {
let stylelintResult = createPartialStylelintResult(postcssResult, cssSyntaxError);
return stylelint.getConfigForFile(filePath).then((configForFile) => {
// TODO TYPES handle possible null here
const config = /** @type {{ config: import('stylelint').StylelintConfig, filepath: string }} */ (configForFile)
.config;
const file = stylelintResult.source || (cssSyntaxError && cssSyntaxError.file);
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, file);
if (returned) {
stylelintResult = returned;
}
});
}
return stylelintResult;
});
};
+62
View File
@@ -0,0 +1,62 @@
'use strict';
const optionsMatches = require('./utils/optionsMatches');
const validateDisableSettings = require('./validateDisableSettings');
/** @typedef {import('postcss/lib/comment')} PostcssComment */
/** @typedef {import('stylelint').RangeType} RangeType */
/** @typedef {import('stylelint').DisableReportRange} DisableReportRange */
/** @typedef {import('stylelint').StylelintDisableOptionsReport} StylelintDisableOptionsReport */
/**
* @param {import('stylelint').StylelintResult[]} results
*/
module.exports = function (results) {
results.forEach((result) => {
const settings = validateDisableSettings(
result._postcssResult,
'reportDescriptionlessDisables',
);
if (!settings) return;
const [enabled, options, stylelintResult] = settings;
const rangeData = stylelintResult.disabledRanges;
/** @type {Set<PostcssComment>} */
const alreadyReported = new Set();
Object.keys(rangeData).forEach((rule) => {
rangeData[rule].forEach((range) => {
if (range.description) return;
if (alreadyReported.has(range.comment)) return;
if (enabled === optionsMatches(options, 'except', rule)) {
// An 'all' rule will get copied for each individual rule. If the
// configuration is `[false, {except: ['specific-rule']}]`, we
// don't want to report the copies that match except, so we record
// the comment as already reported.
if (!enabled && rule === 'all') alreadyReported.add(range.comment);
return;
}
alreadyReported.add(range.comment);
// If the comment doesn't have a location, we can't report a useful error.
// In practice we expect all comments to have locations, though.
if (!range.comment.source || !range.comment.source.start) return;
result.warnings.push({
text: `Disable for "${rule}" is missing a description`,
rule: '--report-descriptionless-disables',
line: range.comment.source.start.line,
column: range.comment.source.start.column,
severity: options.severity,
});
});
});
});
};
+20
View File
@@ -0,0 +1,20 @@
'use strict';
const _ = require('lodash');
/**
* @type {import('stylelint').Formatter}
*/
const formatter = (results) =>
_.flatMap(results, (result) =>
result.warnings.map(
(warning) =>
`${result.source}: ` +
`line ${warning.line}, ` +
`col ${warning.column}, ` +
`${warning.severity} - ` +
`${warning.text}`,
),
).join('\n');
module.exports = formatter;
+12
View File
@@ -0,0 +1,12 @@
'use strict';
const importLazy = require('import-lazy');
module.exports = {
compact: importLazy(() => require('./compactFormatter'))('compactFormatter'),
json: importLazy(() => require('./jsonFormatter'))('jsonFormatter'),
string: importLazy(() => require('./stringFormatter'))('stringFormatter'),
tap: importLazy(() => require('./tapFormatter'))('tapFormatter'),
unix: importLazy(() => require('./unixFormatter'))('unixFormatter'),
verbose: importLazy(() => require('./verboseFormatter'))('verboseFormatter'),
};
+20
View File
@@ -0,0 +1,20 @@
'use strict';
/**
* Omit any properties starting with `_`, which are fake-private
*
* @type {import('stylelint').Formatter}
*/
module.exports = function jsonFormatter(results) {
const cleanedResults = results.map((result) =>
Object.entries(result)
.filter(([key]) => !key.startsWith('_'))
.reduce((/** @type {{ [key: string]: any }} */ obj, [key, value]) => {
obj[key] = value;
return obj;
}, {}),
);
return JSON.stringify(cleanedResults);
};
+225
View File
@@ -0,0 +1,225 @@
'use strict';
const _ = require('lodash');
const chalk = require('chalk');
const path = require('path');
const stringWidth = require('string-width');
const symbols = require('log-symbols');
const table = require('table');
const MARGIN_WIDTHS = 9;
const levelColors = {
info: 'blue',
warning: 'yellow',
error: 'red',
success: undefined,
};
/**
* @param {import('stylelint').StylelintResult[]} results
* @returns {string}
*/
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');
}
/**
* @param {import('stylelint').StylelintResult[]} results
* @return {string}
*/
function invalidOptionsFormatter(results) {
const allInvalidOptionWarnings = _.flatMap(results, (r) =>
r.invalidOptionWarnings.map((w) => w.text),
);
const uniqueInvalidOptionWarnings = [...new Set(allInvalidOptionWarnings)];
return uniqueInvalidOptionWarnings.reduce((output, warning) => {
output += chalk.red('Invalid Option: ');
output += warning;
return `${output}\n`;
}, '\n');
}
/**
* @param {string} fromValue
* @return {string}
*/
function logFrom(fromValue) {
if (fromValue.startsWith('<')) return fromValue;
return path.relative(process.cwd(), fromValue).split(path.sep).join('/');
}
/**
* @param {{[k: number]: number}} columnWidths
* @return {number}
*/
function getMessageWidth(columnWidths) {
if (!process.stdout.isTTY) {
return columnWidths[3];
}
const availableWidth = process.stdout.columns < 80 ? 80 : process.stdout.columns;
const fullWidth = Object.values(columnWidths).reduce((a, b) => a + b);
// 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);
}
/**
* @param {import('stylelint').StylelintWarning[]} messages
* @param {string} source
* @return {string}
*/
function formatter(messages, source) {
if (!messages.length) return '';
const orderedMessages = _.sortBy(
messages,
// eslint-disable-next-line no-confusing-arrow
(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.
* @type {{[k: string]: number}}
*/
const columnWidths = { 0: 1, 1: 1, 2: 1, 3: 1, 4: 1 };
/**
* @param {[string, string, string, string, string]} columns
* @return {[string, string, string, string, string]}
*/
function calculateWidths(columns) {
for (const [key, value] of Object.entries(columns)) {
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 { line, column } = message;
const severity = /** @type {keyof import('log-symbols')} */ (message.severity);
/**
* @type {[string, string, string, string, string]}
*/
const row = [
line ? line.toString() : '',
column ? column.toString() : '',
symbols[severity]
? chalk[/** @type {'blue' | 'red' | 'yellow'} */ (levelColors[severity])](symbols[severity])
: severity,
message.text
// Remove all control characters (newline, tab and etc)
.replace(/[\u0001-\u001A]+/g, ' ') // eslint-disable-line no-control-regex
.replace(/\.$/, '')
// eslint-disable-next-line prefer-template
.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: getMessageWidth(columnWidths) > 1,
},
4: { alignment: 'left', width: columnWidths[4], paddingRight: 0 },
},
drawHorizontalLine: () => false,
})
.split('\n')
.map(
/**
* @param {string} el
* @returns {string}
*/
(el) => el.replace(/(\d+)\s+(\d+)/, (m, p1, p2) => chalk.dim(`${p1}:${p2}`)),
)
.join('\n');
return output;
}
/**
* @type {import('stylelint').Formatter}
*/
module.exports = function (results) {
let output = invalidOptionsFormatter(results);
output += deprecationsFormatter(results);
output = results.reduce((accum, result) => {
// Treat parseErrors as warnings
if (result.parseErrors) {
result.parseErrors.forEach((error) =>
result.warnings.push({
line: error.line,
column: error.column,
rule: error.stylelintType,
severity: 'error',
text: `${error.text} (${error.stylelintType})`,
}),
);
}
accum += formatter(result.warnings, result.source || '');
return accum;
}, output);
// Ensure consistent padding
output = output.trim();
if (output !== '') {
output = `\n${output}\n\n`;
}
return output;
};
+39
View File
@@ -0,0 +1,39 @@
'use strict';
/**
* @type {import('stylelint').Formatter}
*/
const tapFormatter = (results) => {
let lines = [`TAP version 13\n1..${results.length}`];
results.forEach((result, index) => {
lines.push(
`${result.errored ? 'not ok' : 'ok'} ${index + 1} - ${result.ignored ? 'ignored ' : ''}${
result.source
}`,
);
if (result.warnings.length > 0) {
lines.push('---', 'messages:');
result.warnings.forEach((warning) => {
lines.push(
` - message: "${warning.text}"`,
` severity: ${warning.severity}`,
` data:`,
` line: ${warning.line}`,
` column: ${warning.column}`,
` ruleId: ${warning.rule}`,
);
});
lines.push('---');
}
});
lines.push('');
return lines.join('\n');
};
module.exports = tapFormatter;
+26
View File
@@ -0,0 +1,26 @@
'use strict';
const _ = require('lodash');
/**
* @type {import('stylelint').Formatter}
*/
const unixFormatter = (results) => {
const lines = _.flatMap(results, (result) =>
result.warnings.map(
(warning) =>
`${result.source}:${warning.line}:${warning.column}: ` +
`${warning.text} [${warning.severity}]\n`,
),
);
const total = lines.length;
let output = lines.join('');
if (total > 0) {
output += `\n${total} problem${total !== 1 ? 's' : ''}\n`;
}
return output;
};
module.exports = unixFormatter;
+61
View File
@@ -0,0 +1,61 @@
'use strict';
const _ = require('lodash');
const chalk = require('chalk');
const stringFormatter = require('./stringFormatter');
/**
* @type {import('stylelint').Formatter}
*/
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`);
for (const [severityLevel, warningList] of Object.entries(warningsBySeverity)) {
const warningsByRule = _.groupBy(warningList, 'rule');
output += ` severity level "${severityLevel}": ${warningList.length}\n`;
for (const [rule, list] of Object.entries(warningsByRule)) {
output += chalk.dim(` ${rule}: ${list.length}\n`);
}
}
return `${output}\n`;
};
+61
View File
@@ -0,0 +1,61 @@
'use strict';
const augmentConfigFull = require('./augmentConfig').augmentConfigFull;
const configurationError = require('./utils/configurationError');
const path = require('path');
/** @typedef {import('stylelint').StylelintConfig} StylelintConfig */
/** @typedef {import('stylelint').CosmiconfigResult} CosmiconfigResult */
/** @typedef {Promise<CosmiconfigResult | null>} ConfigPromise */
/**
* @param {import('stylelint').StylelintInternalApi} stylelint
* @param {string} [searchPath]
* @returns {ConfigPromise}
*/
module.exports = function (stylelint, searchPath = process.cwd()) {
const optionsConfig = stylelint._options.config;
if (optionsConfig !== undefined) {
const cached = /** @type {ConfigPromise} */ (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;
}
const searchForConfig = stylelint._options.configFile
? stylelint._fullExplorer.load(stylelint._options.configFile)
: stylelint._fullExplorer.search(searchPath);
return /** @type {ConfigPromise} */ (searchForConfig
.then((config) => {
// If no config was found, try looking from process.cwd
if (!config) return stylelint._fullExplorer.search(process.cwd());
return config;
})
.then((config) => {
if (!config) {
const ending = searchPath ? ` for ${searchPath}` : '';
throw configurationError(`No configuration provided${ending}`);
}
return config;
}));
};
+182
View File
@@ -0,0 +1,182 @@
'use strict';
const fs = require('fs');
const LazyResult = require('postcss/lib/lazy-result');
const postcss = require('postcss');
const syntaxes = require('./syntaxes');
/** @typedef {import('postcss').Result} Result */
/** @typedef {import('postcss').Syntax} Syntax */
/** @typedef {import('stylelint').CustomSyntax} CustomSyntax */
/** @typedef {import('stylelint').GetPostcssOptions} GetPostcssOptions */
/** @typedef {import('stylelint').StylelintInternalApi} StylelintInternalApi */
const postcssProcessor = postcss();
/**
* @param {StylelintInternalApi} stylelint
* @param {GetPostcssOptions} options
*
* @returns {Promise<Result>}
*/
module.exports = function (stylelint, options = {}) {
const cached = options.filePath ? stylelint._postcssResultCache.get(options.filePath) : undefined;
if (cached) return Promise.resolve(cached);
/** @type {Promise<string> | undefined} */
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) => {
/** @type {Syntax | null} */
let syntax = null;
if (stylelint._options.customSyntax) {
syntax = getCustomSyntax(stylelint._options.customSyntax);
} else if (stylelint._options.syntax) {
if (stylelint._options.syntax === 'css') {
syntax = cssSyntax(stylelint);
} else {
const keys = Object.keys(syntaxes);
if (!keys.includes(stylelint._options.syntax)) {
throw new Error(
`You must use a valid syntax option, either: css, ${keys
.slice(0, -1)
.join(', ')} or ${keys.slice(-1)}`,
);
}
syntax = syntaxes[stylelint._options.syntax];
}
} else if (!(options.codeProcessors && options.codeProcessors.length)) {
const autoSyntax = require('postcss-syntax');
// TODO: investigate why lazy import HTML syntax causes
// JS files with the word "html" to throw TypeError
// https://github.com/stylelint/stylelint/issues/4793
const { html, ...rest } = syntaxes;
syntax = autoSyntax({
css: cssSyntax(stylelint),
jsx: syntaxes['css-in-js'],
...rest,
});
}
const postcssOptions = {
from: options.filePath,
syntax,
};
const source = options.code ? options.codeFilename : options.filePath;
let preProcessedCode = code;
if (options.codeProcessors && options.codeProcessors.length) {
if (stylelint._options.fix) {
console.warn(
'Autofix is incompatible with processors and will be disabled. Are you sure you need a processor?',
);
stylelint._options.fix = false;
}
options.codeProcessors.forEach((codeProcessor) => {
preProcessedCode = codeProcessor(preProcessedCode, source);
});
}
const result = new LazyResult(postcssProcessor, preProcessedCode, postcssOptions);
return result;
})
.then((postcssResult) => {
if (options.filePath) {
stylelint._postcssResultCache.set(options.filePath, postcssResult);
}
return postcssResult;
});
};
/**
* @param {CustomSyntax} customSyntax
* @returns {Syntax}
*/
function getCustomSyntax(customSyntax) {
let resolved;
if (typeof customSyntax === 'string') {
try {
resolved = require(customSyntax);
} catch {
throw new Error(
`Cannot resolve custom syntax module ${customSyntax}. Check that module ${customSyntax} is available and spelled correctly.`,
);
}
/*
* PostCSS allows for syntaxes that only contain a parser, however,
* it then expects the syntax to be set as the `parse` option.
*/
if (!resolved.parse) {
resolved = {
parse: resolved,
stringify: postcss.stringify,
};
}
return resolved;
}
if (typeof customSyntax === 'object') {
if (typeof customSyntax.parse === 'function') {
resolved = { ...customSyntax };
} else {
throw new TypeError(
`An object provided to the "customSyntax" option must have a "parse" property. Ensure the "parse" property exists and its value is a function.`,
);
}
return resolved;
}
throw new Error(`Custom syntax must be a string or a Syntax object`);
}
/**
* @param {string} filePath
* @returns {Promise<string>}
*/
function readFile(filePath) {
return new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf8', (err, content) => {
if (err) {
return reject(err);
}
resolve(content);
});
});
}
/**
* @param {StylelintInternalApi} stylelint
* @returns {Syntax}
*/
function cssSyntax(stylelint) {
return {
parse: stylelint._options.fix ? require('postcss-safe-parser') : postcss.parse,
stringify: postcss.stringify,
};
}
+33
View File
@@ -0,0 +1,33 @@
'use strict';
const checkAgainstRule = require('./utils/checkAgainstRule');
const createPlugin = require('./createPlugin');
const createStylelint = require('./createStylelint');
const formatters = require('./formatters');
const postcssPlugin = require('./postcssPlugin');
const report = require('./utils/report');
const ruleMessages = require('./utils/ruleMessages');
const rules = require('./rules');
const standalone = require('./standalone');
const validateOptions = require('./utils/validateOptions');
/**
* TODO TYPES change any to appropriated options
* @type {import('postcss').Plugin<any> & Partial<import('stylelint').StylelintPublicAPI>}
*/
const api = postcssPlugin;
api.utils = {
report,
ruleMessages,
validateOptions,
checkAgainstRule,
};
api.lint = standalone;
api.rules = rules;
api.formatters = formatters;
api.createPlugin = createPlugin;
api.createLinter = createStylelint;
module.exports = api;
+50
View File
@@ -0,0 +1,50 @@
'use strict';
const optionsMatches = require('./utils/optionsMatches');
const validateDisableSettings = require('./validateDisableSettings');
/** @typedef {import('stylelint').RangeType} RangeType */
/**
* @param {import('stylelint').StylelintResult[]} results
*/
module.exports = function (results) {
results.forEach((result) => {
const settings = validateDisableSettings(result._postcssResult, 'reportInvalidScopeDisables');
if (!settings) return;
const [enabled, options, stylelintResult] = settings;
const configRules = (stylelintResult.config || {}).rules || {};
const usedRules = new Set(Object.keys(configRules));
usedRules.add('all');
const rangeData = stylelintResult.disabledRanges;
const disabledRules = Object.keys(rangeData);
disabledRules.forEach((rule) => {
if (usedRules.has(rule)) return;
if (enabled === optionsMatches(options, 'except', rule)) return;
rangeData[rule].forEach((range) => {
if (!range.strictStart && !range.strictEnd) return;
// If the comment doesn't have a location, we can't report a useful error.
// In practice we expect all comments to have locations, though.
if (!range.comment.source || !range.comment.source.start) return;
result.warnings.push({
text: `Rule "${rule}" isn't enabled`,
rule: '--report-invalid-scope-disables',
line: range.comment.source.start.line,
column: range.comment.source.start.column,
severity: options.severity,
});
});
});
});
};
+48
View File
@@ -0,0 +1,48 @@
'use strict';
const filterFilePaths = require('./utils/filterFilePaths');
const getFileIgnorer = require('./utils/getFileIgnorer');
const micromatch = require('micromatch');
const path = require('path');
const slash = require('slash');
/**
* To find out if a path is ignored, we need to load the config,
* which may have an ignoreFiles property. We then check the path
* against these.
* @param {import('stylelint').StylelintInternalApi} stylelint
* @param {string} [filePath]
* @return {Promise<boolean>}
*/
module.exports = function (stylelint, filePath) {
if (!filePath) {
return Promise.resolve(false);
}
const cwd = process.cwd();
const ignorer = getFileIgnorer(stylelint._options);
return stylelint.getConfigForFile(filePath).then((result) => {
if (!result) {
return true;
}
// Glob patterns for micromatch should be in POSIX-style
const ignoreFiles = /** @type {Array<string>} */ (result.config.ignoreFiles || []).map(slash);
const absoluteFilePath = path.isAbsolute(filePath)
? filePath
: path.resolve(process.cwd(), filePath);
if (micromatch([absoluteFilePath], ignoreFiles).length) {
return true;
}
// Check filePath with .stylelintignore file
if (filterFilePaths(ignorer, [path.relative(cwd, absoluteFilePath)]).length === 0) {
return true;
}
return false;
});
};
+134
View File
@@ -0,0 +1,134 @@
'use strict';
const assignDisabledRanges = require('./assignDisabledRanges');
const get = require('lodash/get');
const getOsEol = require('./utils/getOsEol');
const reportUnknownRuleNames = require('./reportUnknownRuleNames');
const rulesOrder = require('./rules');
/** @typedef {import('stylelint').StylelintStandaloneOptions} StylelintStandaloneOptions */
/** @typedef {import('stylelint').PostcssResult} PostcssResult */
/** @typedef {import('stylelint').StylelintConfig} StylelintConfig */
/**
* @param {StylelintStandaloneOptions} stylelintOptions
* @param {PostcssResult} postcssResult
* @param {StylelintConfig} config
* @returns {Promise<any>}
*/
function lintPostcssResult(stylelintOptions, postcssResult, config) {
postcssResult.stylelint.ruleSeverities = {};
postcssResult.stylelint.customMessages = {};
postcssResult.stylelint.stylelintError = false;
postcssResult.stylelint.quiet = config.quiet;
postcssResult.stylelint.config = config;
/** @type {string} */
let newline;
const postcssDoc = postcssResult.root;
if (postcssDoc) {
if (!('type' in postcssDoc)) {
throw new Error('Unexpected Postcss root object!');
}
// @ts-ignore TODO TYPES property css does not exists
const newlineMatch = postcssDoc.source && postcssDoc.source.input.css.match(/\r?\n/);
newline = newlineMatch ? newlineMatch[0] : getOsEol();
assignDisabledRanges(postcssDoc, postcssResult);
}
const isFileFixCompatible = isFixCompatible(postcssResult);
if (!isFileFixCompatible) {
postcssResult.stylelint.disableWritingFix = true;
}
const postcssRoots = /** @type {import('postcss').Root[]} */ (postcssDoc &&
postcssDoc.constructor.name === 'Document'
? postcssDoc.nodes
: [postcssDoc]);
// 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.
/** @type {Array<Promise<any>>} */
const performRules = [];
const rules = config.rules
? Object.keys(config.rules).sort(
(a, b) => Object.keys(rulesOrder).indexOf(a) - Object.keys(rulesOrder).indexOf(b),
)
: [];
rules.forEach((ruleName) => {
const ruleFunction = rulesOrder[ruleName] || get(config, ['pluginFunctions', ruleName]);
if (ruleFunction === undefined) {
performRules.push(
Promise.all(
postcssRoots.map((postcssRoot) =>
reportUnknownRuleNames(ruleName, postcssRoot, postcssResult),
),
),
);
return;
}
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');
performRules.push(
Promise.all(
postcssRoots.map((postcssRoot) =>
ruleFunction(primaryOption, secondaryOptions, {
fix:
stylelintOptions.fix &&
// Next two conditionals are temporary measures until #2643 is resolved
isFileFixCompatible &&
!postcssResult.stylelint.disabledRanges[ruleName],
newline,
})(postcssRoot, postcssResult),
),
),
);
});
return Promise.all(performRules);
}
/**
* There are currently some bugs in the autofixer of Stylelint.
* The autofixer does not yet adhere to stylelint-disable comments, so if there are disabled
* ranges we can not autofix this document. More info in issue #2643.
*
* @param {PostcssResult} postcssResult
* @returns {boolean}
*/
function isFixCompatible({ stylelint }) {
// Check for issue #2643
if (stylelint.disabledRanges.all.length) return false;
return true;
}
module.exports = lintPostcssResult;
+133
View File
@@ -0,0 +1,133 @@
'use strict';
const lintPostcssResult = require('./lintPostcssResult');
const path = require('path');
/** @typedef {import('stylelint').StylelintInternalApi} StylelintInternalApi */
/** @typedef {import('stylelint').GetLintSourceOptions} Options */
/** @typedef {import('postcss').Result} Result */
/** @typedef {import('stylelint').PostcssResult} PostcssResult */
/** @typedef {import('stylelint').StylelintPostcssResult} StylelintPostcssResult */
/**
* Run stylelint on a PostCSS Result, either one that is provided
* or one that we create
* @param {StylelintInternalApi} stylelint
* @param {Options} options
* @returns {Promise<PostcssResult>}
*/
module.exports = function lintSource(stylelint, 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'));
}
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) {
/** @type {PostcssResult} */
return options.existingPostcssResult
? Object.assign(options.existingPostcssResult, {
stylelint: createEmptyStylelintPostcssResult(),
})
: createEmptyPostcssResult(inputFilePath);
}
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) => {
if (!result) {
throw new Error('Config file not found');
}
const config = result.config;
const existingPostcssResult = options.existingPostcssResult;
const stylelintResult = {
ruleSeverities: {},
customMessages: {},
disabledRanges: {},
};
if (existingPostcssResult) {
const stylelintPostcssResult = Object.assign(existingPostcssResult, {
stylelint: stylelintResult,
});
return lintPostcssResult(stylelint._options, stylelintPostcssResult, config).then(
() => stylelintPostcssResult,
);
}
return stylelint
._getPostcssResult({
code: options.code,
codeFilename: options.codeFilename,
filePath: inputFilePath,
codeProcessors: config.codeProcessors,
})
.then((postcssResult) => {
const stylelintPostcssResult = Object.assign(postcssResult, {
stylelint: stylelintResult,
});
return lintPostcssResult(stylelint._options, stylelintPostcssResult, config).then(
() => stylelintPostcssResult,
);
});
});
});
};
/**
* @returns {StylelintPostcssResult}
*/
function createEmptyStylelintPostcssResult() {
return {
ruleSeverities: {},
customMessages: {},
disabledRanges: {},
ignored: true,
stylelintError: false,
};
}
/**
* @param {string} [filePath]
* @returns {PostcssResult}
*/
function createEmptyPostcssResult(filePath) {
return {
root: {
source: {
input: { file: filePath },
},
},
messages: [],
opts: undefined,
stylelint: createEmptyStylelintPostcssResult(),
warn: () => {},
};
}
+110
View File
@@ -0,0 +1,110 @@
'use strict';
const _ = require('lodash');
const optionsMatches = require('./utils/optionsMatches');
const putIfAbsent = require('./utils/putIfAbsent');
const validateDisableSettings = require('./validateDisableSettings');
/** @typedef {import('postcss/lib/comment')} PostcssComment */
/** @typedef {import('stylelint').DisabledRange} DisabledRange */
/** @typedef {import('stylelint').RangeType} RangeType */
/** @typedef {import('stylelint').DisableReportRange} DisableReportRange */
/**
* @param {import('stylelint').StylelintResult[]} results
*/
module.exports = function (results) {
results.forEach((result) => {
const settings = validateDisableSettings(result._postcssResult, 'reportNeedlessDisables');
if (!settings) return;
const [enabled, options, stylelintResult] = settings;
/** @type {{[ruleName: string]: Array<DisabledRange>}} */
const rangeData = _.cloneDeep(stylelintResult.disabledRanges);
if (!rangeData) return;
const disabledWarnings = stylelintResult.disabledWarnings || [];
// A map from `stylelint-disable` comments to the set of rules that
// are usefully disabled by each comment. We track this
// comment-by-comment rather than range-by-range because ranges that
// disable *all* rules are duplicated for each rule they apply to in
// practice.
/** @type {Map<PostcssComment, Set<string>>}} */
const usefulDisables = new Map();
for (const warning of disabledWarnings) {
const rule = warning.rule;
const ruleRanges = rangeData[rule];
if (ruleRanges) {
for (const range of ruleRanges) {
if (isWarningInRange(warning, range)) {
putIfAbsent(usefulDisables, range.comment, () => new Set()).add(rule);
}
}
}
for (const range of rangeData.all) {
if (isWarningInRange(warning, range)) {
putIfAbsent(usefulDisables, range.comment, () => new Set()).add(rule);
}
}
}
const rangeEntries = Object.entries(rangeData);
// Get rid of the duplicated ranges for each `all` rule. We only care
// if the entire `all` rule is useful as a whole or not.
for (const range of rangeData.all) {
for (const [rule, ranges] of rangeEntries) {
if (rule === 'all') continue;
_.remove(ranges, (otherRange) => range.comment === otherRange.comment);
}
}
for (const [rule, ranges] of rangeEntries) {
for (const range of ranges) {
if (enabled === optionsMatches(options, 'except', rule)) continue;
const useful = usefulDisables.get(range.comment) || new Set();
// Only emit a warning if this range's comment isn't useful for this rule.
// For the special rule "all", only emit a warning if it's not useful for
// *any* rules, because it covers all of them.
if (rule === 'all' ? useful.size !== 0 : useful.has(rule)) continue;
// If the comment doesn't have a location, we can't report a useful error.
// In practice we expect all comments to have locations, though.
if (!range.comment.source || !range.comment.source.start) continue;
result.warnings.push({
text: `Needless disable for "${rule}"`,
rule: '--report-needless-disables',
line: range.comment.source.start.line,
column: range.comment.source.start.column,
severity: options.severity,
});
}
}
});
};
/**
* @param {import('stylelint').DisabledWarning} warning
* @param {RangeType} range
* @return {boolean}
*/
function isWarningInRange(warning, range) {
const line = warning.line;
// Need to check if range.end exist, because line number type cannot be compared to undefined
return (
range.start <= line &&
((range.end !== undefined && range.end >= line) || range.end === undefined)
);
}
+41
View File
@@ -0,0 +1,41 @@
'use strict';
const _ = require('lodash');
const normalizeRuleSettings = require('./normalizeRuleSettings');
const rules = require('./rules');
/** @typedef {import('stylelint').StylelintConfigRules} StylelintConfigRules */
/** @typedef {import('stylelint').StylelintConfig} StylelintConfig */
/**
* @param {StylelintConfig} config
* @return {StylelintConfig}
*/
function normalizeAllRuleSettings(config) {
/** @type {StylelintConfigRules} */
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) {
normalizedRules[ruleName] = [];
} else {
normalizedRules[ruleName] = normalizeRuleSettings(
rawRuleSettings,
ruleName,
_.get(rule, 'primaryOptionArray'),
);
}
});
config.rules = normalizedRules;
return config;
}
module.exports = normalizeAllRuleSettings;
+74
View File
@@ -0,0 +1,74 @@
'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
* @template T
* @template {Object} O
* @param {import('stylelint').StylelintConfigRuleSettings<T, O>} rawSettings
* @param {string} ruleName
* @param {boolean} [primaryOptionArray] If primaryOptionArray is not provided, we try to get it from the rules themselves, which will not work for plugins
* @return {[T] | [T, O] | null}
*/
module.exports = function (
rawSettings,
ruleName,
// If primaryOptionArray is not provided, we try to get it from the
// rules themselves, which will not work for plugins
primaryOptionArray,
) {
if (_.isNil(rawSettings)) {
return null;
}
if (!Array.isArray(rawSettings)) {
return [rawSettings];
}
// Everything below is an array ...
if (!_.isEmpty(rawSettings) && _.isNil(rawSettings[0])) {
return null;
}
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;
}
// `T` must be an array type, but TSC thinks it's probably invalid to
// cast `[T]` to `T` so we cast through `any` first.
return [/** @type {T} */ (/** @type {any} */ (rawSettings))];
};
+25
View File
@@ -0,0 +1,25 @@
'use strict';
const _ = require('lodash');
const createStylelint = require('./createStylelint');
const path = require('path');
const postcss = require('postcss');
//'block-no-empty': bool || Array
module.exports = postcss.plugin('stylelint', (options = {}) => {
const tailoredOptions = 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,
});
};
});
+59
View File
@@ -0,0 +1,59 @@
'use strict';
const descriptionlessDisables = require('./descriptionlessDisables');
const invalidScopeDisables = require('./invalidScopeDisables');
const needlessDisables = require('./needlessDisables');
const reportDisables = require('./reportDisables');
/** @typedef {import('stylelint').Formatter} Formatter */
/** @typedef {import('stylelint').StylelintResult} StylelintResult */
/** @typedef {import('stylelint').StylelintStandaloneOptions} StylelintStandaloneOptions */
/** @typedef {import('stylelint').StylelintStandaloneReturnValue} StylelintStandaloneReturnValue */
/**
* @param {StylelintResult[]} stylelintResults
* @param {StylelintStandaloneOptions} options
* @param {Formatter} formatter
*
* @returns {StylelintStandaloneReturnValue}
*/
function prepareReturnValue(stylelintResults, options, formatter) {
const { maxWarnings } = options;
reportDisables(stylelintResults);
needlessDisables(stylelintResults);
invalidScopeDisables(stylelintResults);
descriptionlessDisables(stylelintResults);
const errored = stylelintResults.some(
(result) =>
result.errored ||
result.parseErrors.length > 0 ||
result.warnings.some((warning) => warning.severity === 'error'),
);
/** @type {StylelintStandaloneReturnValue} */
const returnValue = {
errored,
results: [],
output: '',
reportedDisables: [],
};
if (maxWarnings !== undefined) {
const foundWarnings = stylelintResults.reduce((count, file) => {
return count + file.warnings.length;
}, 0);
if (foundWarnings > maxWarnings) {
returnValue.maxWarningsExceeded = { maxWarnings, foundWarnings };
}
}
returnValue.output = formatter(stylelintResults, returnValue);
returnValue.results = stylelintResults;
return returnValue;
}
module.exports = prepareReturnValue;
+58
View File
@@ -0,0 +1,58 @@
'use strict';
const _ = require('lodash');
const createStylelint = require('./createStylelint');
const globby = require('globby');
const path = require('path');
/** @typedef {import('stylelint').StylelintConfig} StylelintConfig */
/**
* @param {import('stylelint').StylelintStandaloneOptions} options
* @returns {Promise<StylelintConfig | null>}
*/
module.exports = function (options) {
const code = options.code;
const config = options.config;
const configBasedir = options.configBasedir;
const configFile = options.configFile;
const configOverrides = options.configOverrides;
const globbyOptions = options.globbyOptions;
const files = options.files;
const isCodeNotFile = code !== undefined;
if (!files || files.length !== 1 || isCodeNotFile) {
return Promise.reject(
new Error('The --print-config option must be used with exactly one file path.'),
);
}
const filePath = files[0];
if (globby.hasMagic(filePath)) {
return Promise.reject(new Error('The --print-config option does not support globs.'));
}
const stylelint = createStylelint({
config,
configFile,
configBasedir,
configOverrides,
});
const cwd = _.get(globbyOptions, 'cwd', process.cwd());
const absoluteFilePath = !path.isAbsolute(filePath)
? path.join(cwd, filePath)
: path.normalize(filePath);
const configSearchPath = stylelint._options.configFile || absoluteFilePath;
return stylelint.getConfigForFile(configSearchPath).then((result) => {
if (result === null) {
return result;
}
return result.config;
});
};
+690
View File
@@ -0,0 +1,690 @@
'use strict';
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',
'rlh',
'lh',
// Viewport-percentage lengths
'vh',
'vw',
'vmin',
'vmax',
'vm',
// Absolute length units
'px',
'mm',
'cm',
'in',
'pt',
'pc',
'q',
'mozmm',
// 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.systemFontValues = uniteSets(keywordSets.basicKeywords, [
'caption',
'icon',
'menu',
'message-box',
'small-caption',
'status-bar',
]);
keywordSets.fontFamilyKeywords = uniteSets(keywordSets.basicKeywords, [
'serif',
'sans-serif',
'cursive',
'fantasy',
'monospace',
'system-ui',
]);
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.levelThreeAndUpPseudoElements = new Set([
'before',
'after',
'first-line',
'first-letter',
'selection',
'spelling-error',
'grammar-error',
'backdrop',
'marker',
'placeholder',
'shadow',
'slotted',
'content',
'file-selector-button',
]);
keywordSets.shadowTreePseudoElements = new Set(['part']);
keywordSets.vendorSpecificPseudoElements = new Set([
'-moz-progress-bar',
'-moz-range-progress',
'-moz-range-thumb',
'-moz-range-track',
'-ms-browse',
'-ms-check',
'-ms-clear',
'-ms-expand',
'-ms-fill',
'-ms-fill-lower',
'-ms-fill-upper',
'-ms-reveal',
'-ms-thumb',
'-ms-ticks-after',
'-ms-ticks-before',
'-ms-tooltip',
'-ms-track',
'-ms-value',
'-webkit-progress-bar',
'-webkit-progress-value',
'-webkit-slider-runnable-track',
'-webkit-slider-thumb',
]);
keywordSets.pseudoElements = uniteSets(
keywordSets.levelOneAndTwoPseudoElements,
keywordSets.levelThreeAndUpPseudoElements,
keywordSets.vendorSpecificPseudoElements,
keywordSets.shadowTreePseudoElements,
);
keywordSets.aNPlusBNotationPseudoClasses = new Set([
'nth-column',
'nth-last-column',
'nth-last-of-type',
'nth-of-type',
]);
keywordSets.linguisticPseudoClasses = new Set(['dir', 'lang']);
keywordSets.atRulePagePseudoClasses = new Set(['first', 'right', 'left', 'blank']);
keywordSets.logicalCombinationsPseudoClasses = new Set(['has', 'is', 'matches', 'not', 'where']);
keywordSets.aNPlusBOfSNotationPseudoClasses = new Set(['nth-child', 'nth-last-child']);
keywordSets.otherPseudoClasses = new Set([
'active',
'any-link',
'autofill',
'blank',
'checked',
/*
https://www.w3.org/Style/CSS/Test/CSS3/Selectors/20011105/html/tests/css3-modsel-85.html
https://www.w3.org/Style/CSS/Test/CSS3/Selectors/20011105/html/tests/css3-modsel-84.html
*/
'contains',
'current',
'default',
'defined',
'disabled',
'drop',
'empty',
'enabled',
'first-child',
'first-of-type',
'focus',
'focus-ring',
'focus-within',
'focus-visible',
'fullscreen',
'future',
'host',
'host-context',
'hover',
'indeterminate',
'in-range',
'invalid',
'last-child',
'last-of-type',
'link',
'only-child',
'only-of-type',
'optional',
'out-of-range',
'past',
'placeholder-shown',
'playing',
'paused',
'read-only',
'read-write',
'required',
'root',
'scope',
'state',
'target',
'user-error',
'user-invalid',
'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.logicalCombinationsPseudoClasses,
keywordSets.aNPlusBOfSNotationPseudoClasses,
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.counterResetKeywords = 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://www.w3.org/TR/css-page-3/#syntax-page-selector
keywordSets.pageMarginAtRules = new Set([
'top-left-corner',
'top-left',
'top-center',
'top-right',
'top-right-corner',
'bottom-left-corner',
'bottom-left',
'bottom-center',
'bottom-right',
'bottom-right-corner',
'left-top',
'left-middle',
'left-bottom',
'right-top',
'right-middle',
'right-bottom',
]);
// https://developer.mozilla.org/en/docs/Web/CSS/At-rule
keywordSets.atRules = uniteSets(keywordSets.pageMarginAtRules, [
'annotation',
'apply',
'character-variant',
'charset',
'counter-style',
'custom-media',
'custom-selector',
'document',
'font-face',
'font-feature-values',
'import',
'keyframes',
'media',
'namespace',
'nest',
'ornaments',
'page',
'property',
'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',
'forced-colors',
'grid',
'height',
'hover',
'inverted-colors',
'light-level',
'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',
'prefers-color-scheme',
'prefers-reduced-motion',
'prefers-reduced-transparency',
'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',
]);
// htmlTags includes only "standard" tags. So we augment it with older tags etc.
keywordSets.nonStandardHtmlTags = new Set([
'acronym',
'applet',
'basefont',
'big',
'blink',
'center',
'content',
'dir',
'font',
'frame',
'frameset',
'hgroup',
'isindex',
'keygen',
'listing',
'marquee',
'nobr',
'noembed',
'plaintext',
'spacer',
'strike',
'tt',
'xmp',
]);
/**
* @param {(string[] | Set<string>)[]} args
*/
function uniteSets(...args) {
return new Set(
[...args].reduce((/** @type {string[]} */ result, set) => {
return result.concat([...set]);
}, []),
);
}
module.exports = keywordSets;
+3
View File
@@ -0,0 +1,3 @@
'use strict';
module.exports = ['calc', 'clamp', 'max', 'min'];
+152
View File
@@ -0,0 +1,152 @@
'use strict';
module.exports = {
aliceblue: ['#f0f8ff', '#fff0f8ff'],
antiquewhite: ['#faebd7', '#fffaebd7'],
aqua: ['#00ffff', '#ff00ffff', '#0ff', '#f0ff'],
aquamarine: ['#7fffd4', '#ff7fffd4'],
azure: ['#f0ffff', '#fff0ffff'],
beige: ['#f5f5dc', '#fff5f5dc'],
bisque: ['#ffe4c4', '#ffffe4c4'],
black: ['#000000', '#ff000000', '#000', '#f000'],
blanchedalmond: ['#ffebcd', '#ffffebcd'],
blue: ['#0000ff', '#ff0000ff', '#00f', '#f00f'],
blueviolet: ['#8a2be2', '#ff8a2be2'],
brown: ['#a52a2a', '#ffa52a2a'],
burlywood: ['#deb887', '#ffdeb887'],
cadetblue: ['#5f9ea0', '#ff5f9ea0'],
chartreuse: ['#7fff00', '#ff7fff00'],
chocolate: ['#d2691e', '#ffd2691e'],
coral: ['#ff7f50', '#ffff7f50'],
cornflowerblue: ['#6495ed', '#ff6495ed'],
cornsilk: ['#fff8dc', '#fffff8dc'],
crimson: ['#dc143c', '#ffdc143c'],
cyan: ['#00ffff', '#ff00ffff', '#0ff', '#f0ff'],
darkblue: ['#00008b', '#ff00008b'],
darkcyan: ['#008b8b', '#ff008b8b'],
darkgoldenrod: ['#b8860b', '#ffb8860b'],
darkgray: ['#a9a9a9', '#ffa9a9a9'],
darkgreen: ['#006400', '#ff006400'],
darkgrey: ['#a9a9a9', '#ffa9a9a9'],
darkkhaki: ['#bdb76b', '#ffbdb76b'],
darkmagenta: ['#8b008b', '#ff8b008b'],
darkolivegreen: ['#556b2f', '#ff556b2f'],
darkorange: ['#ff8c00', '#ffff8c00'],
darkorchid: ['#9932cc', '#ff9932cc'],
darkred: ['#8b0000', '#ff8b0000'],
darksalmon: ['#e9967a', '#ffe9967a'],
darkseagreen: ['#8fbc8f', '#ff8fbc8f'],
darkslateblue: ['#483d8b', '#ff483d8b'],
darkslategray: ['#2f4f4f', '#ff2f4f4f'],
darkslategrey: ['#2f4f4f', '#ff2f4f4f'],
darkturquoise: ['#00ced1', '#ff00ced1'],
darkviolet: ['#9400d3', '#ff9400d3'],
deeppink: ['#ff1493', '#ffff1493'],
deepskyblue: ['#00bfff', '#ff00bfff'],
dimgray: ['#696969', '#ff696969'],
dimgrey: ['#696969', '#ff696969'],
dodgerblue: ['#1e90ff', '#ff1e90ff'],
firebrick: ['#b22222', '#ffb22222'],
floralwhite: ['#fffaf0', '#fffffaf0'],
forestgreen: ['#228b22', '#ff228b22'],
fuchsia: ['#ff00ff', '#ffff00ff', '#f0f', '#ff0f'],
gainsboro: ['#dcdcdc', '#ffdcdcdc'],
ghostwhite: ['#f8f8ff', '#fff8f8ff'],
gold: ['#ffd700', '#ffffd700'],
goldenrod: ['#daa520', '#ffdaa520'],
gray: ['#808080', '#ff808080'],
green: ['#008000', '#ff008000'],
greenyellow: ['#adff2f', '#ffadff2f'],
grey: ['#808080', '#ff808080'],
honeydew: ['#f0fff0', '#fff0fff0'],
hotpink: ['#ff69b4', '#ffff69b4'],
indianred: ['#cd5c5c', '#ffcd5c5c'],
indigo: ['#4b0082', '#ff4b0082'],
ivory: ['#fffff0', '#fffffff0'],
khaki: ['#f0e68c', '#fff0e68c'],
lavender: ['#e6e6fa', '#ffe6e6fa'],
lavenderblush: ['#fff0f5', '#fffff0f5'],
lawngreen: ['#7cfc00', '#ff7cfc00'],
lemonchiffon: ['#fffacd', '#fffffacd'],
lightblue: ['#add8e6', '#ffadd8e6'],
lightcoral: ['#f08080', '#fff08080'],
lightcyan: ['#e0ffff', '#ffe0ffff'],
lightgoldenrodyellow: ['#fafad2', '#fffafad2'],
lightgray: ['#d3d3d3', '#ffd3d3d3'],
lightgreen: ['#90ee90', '#ff90ee90'],
lightgrey: ['#d3d3d3', '#ffd3d3d3'],
lightpink: ['#ffb6c1', '#ffffb6c1'],
lightsalmon: ['#ffa07a', '#ffffa07a'],
lightseagreen: ['#20b2aa', '#ff20b2aa'],
lightskyblue: ['#87cefa', '#ff87cefa'],
lightslategray: ['#778899', '#ff778899', '#789', '#f789'],
lightslategrey: ['#778899', '#ff778899', '#789', '#f789'],
lightsteelblue: ['#b0c4de', '#ffb0c4de'],
lightyellow: ['#ffffe0', '#ffffffe0'],
lime: ['#00ff00', '#ff00ff00', '#0f0', '#f0f0'],
limegreen: ['#32cd32', '#ff32cd32'],
linen: ['#faf0e6', '#fffaf0e6'],
magenta: ['#ff00ff', '#ffff00ff', '#f0f', '#ff0f'],
maroon: ['#800000', '#ff800000'],
mediumaquamarine: ['#66cdaa', '#ff66cdaa'],
mediumblue: ['#0000cd', '#ff0000cd'],
mediumorchid: ['#ba55d3', '#ffba55d3'],
mediumpurple: ['#9370db', '#ff9370db'],
mediumseagreen: ['#3cb371', '#ff3cb371'],
mediumslateblue: ['#7b68ee', '#ff7b68ee'],
mediumspringgreen: ['#00fa9a', '#ff00fa9a'],
mediumturquoise: ['#48d1cc', '#ff48d1cc'],
mediumvioletred: ['#c71585', '#ffc71585'],
midnightblue: ['#191970', '#ff191970'],
mintcream: ['#f5fffa', '#fff5fffa'],
mistyrose: ['#ffe4e1', '#ffffe4e1'],
moccasin: ['#ffe4b5', '#ffffe4b5'],
navajowhite: ['#ffdead', '#ffffdead'],
navy: ['#000080', '#ff000080'],
oldlace: ['#fdf5e6', '#fffdf5e6'],
olive: ['#808000', '#ff808000'],
olivedrab: ['#6b8e23', '#ff6b8e23'],
orange: ['#ffa500', '#ffffa500'],
orangered: ['#ff4500', '#ffff4500'],
orchid: ['#da70d6', '#ffda70d6'],
palegoldenrod: ['#eee8aa', '#ffeee8aa'],
palegreen: ['#98fb98', '#ff98fb98'],
paleturquoise: ['#afeeee', '#ffafeeee'],
palevioletred: ['#db7093', '#ffdb7093'],
papayawhip: ['#ffefd5', '#ffffefd5'],
peachpuff: ['#ffdab9', '#ffffdab9'],
peru: ['#cd853f', '#ffcd853f'],
pink: ['#ffc0cb', '#ffffc0cb'],
plum: ['#dda0dd', '#ffdda0dd'],
powderblue: ['#b0e0e6', '#ffb0e0e6'],
purple: ['#800080', '#ff800080'],
rebeccapurple: ['#663399', '#ff663399', '#639', '#f639'],
red: ['#ff0000', '#ffff0000', '#f00', '#ff00'],
rosybrown: ['#bc8f8f', '#ffbc8f8f'],
royalblue: ['#4169e1', '#ff4169e1'],
saddlebrown: ['#8b4513', '#ff8b4513'],
salmon: ['#fa8072', '#fffa8072'],
sandybrown: ['#f4a460', '#fff4a460'],
seagreen: ['#2e8b57', '#ff2e8b57'],
seashell: ['#fff5ee', '#fffff5ee'],
sienna: ['#a0522d', '#ffa0522d'],
silver: ['#c0c0c0', '#ffc0c0c0'],
skyblue: ['#87ceeb', '#ff87ceeb'],
slateblue: ['#6a5acd', '#ff6a5acd'],
slategray: ['#708090', '#ff708090'],
slategrey: ['#708090', '#ff708090'],
snow: ['#fffafa', '#fffffafa'],
springgreen: ['#00ff7f', '#ff00ff7f'],
steelblue: ['#4682b4', '#ff4682b4'],
tan: ['#d2b48c', '#ffd2b48c'],
teal: ['#008080', '#ff008080'],
thistle: ['#d8bfd8', '#ffd8bfd8'],
tomato: ['#ff6347', '#ffff6347'],
turquoise: ['#40e0d0', '#ff40e0d0'],
violet: ['#ee82ee', '#ffee82ee'],
wheat: ['#f5deb3', '#fff5deb3'],
white: ['#ffffff', '#ffffffff', '#fff', '#ffff'],
whitesmoke: ['#f5f5f5', '#fff5f5f5'],
yellow: ['#ffff00', '#ffffff00', '#ff0', '#fff0'],
yellowgreen: ['#9acd32', '#ff9acd32'],
};
+18
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;
+9
View File
@@ -0,0 +1,9 @@
'use strict';
const punctuationSets = {};
punctuationSets.mediaFeaturePunctuation = new Set([':', '=', '>', '>=', '<', '<=']);
punctuationSets.nonSpaceCombinators = new Set(['>', '+', '~', '>>>', '/deep/']);
module.exports = punctuationSets;
+143
View File
@@ -0,0 +1,143 @@
'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',
],
animation: [
'animation-name',
'animation-duration',
'animation-timing-function',
'animation-delay',
'animation-iteration-count',
'animation-direction',
'animation-fill-mode',
'animation-play-state',
],
'border-block-end': [
'border-block-end-width',
'border-block-end-style',
'border-block-end-color',
],
'border-block-start': [
'border-block-start-width',
'border-block-start-style',
'border-block-start-color',
],
'border-image': [
'border-image-source',
'border-image-slice',
'border-image-width',
'border-image-outset',
'border-image-repeat',
],
'border-inline-end': [
'border-inline-end-width',
'border-inline-end-style',
'border-inline-end-color',
],
'border-inline-start': [
'border-inline-start-width',
'border-inline-start-style',
'border-inline-start-color',
],
'column-rule': ['column-rule-width', 'column-rule-style', 'column-rule-color'],
columns: ['column-width', 'column-count'],
flex: ['flex-grow', 'flex-shrink', 'flex-basis'],
'flex-flow': ['flex-direction', 'flex-wrap'],
grid: [
'grid-template-rows',
'grid-template-columns',
'grid-template-areas',
'grid-auto-rows',
'grid-auto-columns',
'grid-auto-flow',
'grid-column-gap',
'grid-row-gap',
],
'grid-area': ['grid-row-start', 'grid-column-start', 'grid-row-end', 'grid-column-end'],
'grid-column': ['grid-column-start', 'grid-column-end'],
'grid-gap': ['grid-row-gap', 'grid-column-gap'],
'grid-row': ['grid-row-start', 'grid-row-end'],
'grid-template': ['grid-template-columns', 'grid-template-rows', 'grid-template-areas'],
outline: ['outline-color', 'outline-style', 'outline-width'],
'text-decoration': ['text-decoration-color', 'text-decoration-style', 'text-decoration-line'],
'text-emphasis': ['text-emphasis-style', 'text-emphasis-color'],
mask: [
'mask-image',
'mask-mode',
'mask-position',
'mask-size',
'mask-repeat',
'mask-origin',
'mask-clip',
'mask-composite',
],
};
+62
View File
@@ -0,0 +1,62 @@
'use strict';
const _ = require('lodash');
/** @typedef {import('stylelint').RangeType} RangeType */
/** @typedef {import('stylelint').DisableReportRange} DisabledRange */
/**
* Returns a report describing which `results` (if any) contain disabled ranges
* for rules that disallow disables via `reportDisables: true`.
*
* @param {import('stylelint').StylelintResult[]} results
*/
module.exports = function (results) {
results.forEach((result) => {
// File with `CssSyntaxError` don't have `_postcssResult`s.
if (!result._postcssResult) {
return;
}
/** @type {{[ruleName: string]: Array<RangeType>}} */
const rangeData = result._postcssResult.stylelint.disabledRanges;
if (!rangeData) return;
const config = result._postcssResult.stylelint.config;
// If no rules actually disallow disables, don't bother looking for ranges
// that correspond to disabled rules.
if (!Object.values(_.get(config, 'rules', {})).some(reportDisablesForRule)) {
return [];
}
Object.keys(rangeData).forEach((rule) => {
rangeData[rule].forEach((range) => {
if (!reportDisablesForRule(_.get(config, ['rules', rule], []))) return;
// If the comment doesn't have a location, we can't report a useful error.
// In practice we expect all comments to have locations, though.
if (!range.comment.source || !range.comment.source.start) return;
result.warnings.push({
text: `Rule "${rule}" may not be disabled`,
rule: 'reportDisables',
line: range.comment.source.start.line,
column: range.comment.source.start.column,
severity: 'error',
});
});
});
});
};
/**
* @param {[any, object]|null} options
* @return {boolean}
*/
function reportDisablesForRule(options) {
if (!options) return false;
return _.get(options[1], 'reportDisables', false);
}
+76
View File
@@ -0,0 +1,76 @@
'use strict';
const levenshtein = require('fastest-levenshtein');
const rules = require('./rules');
const MAX_LEVENSHTEIN_DISTANCE = 6;
const MAX_SUGGESTIONS_COUNT = 3;
/**
* @param {string} ruleName
* @return {string[]}
*/
function extractSuggestions(ruleName) {
const suggestions = new Array(MAX_LEVENSHTEIN_DISTANCE);
for (let i = 0; i < suggestions.length; i++) {
suggestions[i] = [];
}
Object.keys(rules).forEach((existRuleName) => {
const distance = levenshtein.distance(existRuleName, ruleName);
if (distance <= MAX_LEVENSHTEIN_DISTANCE) {
suggestions[distance - 1].push(existRuleName);
}
});
/** @type {string[]} */
let result = [];
for (let i = 0; i < suggestions.length; i++) {
if (suggestions[i].length > 0) {
if (i < 3) {
return suggestions[i].slice(0, MAX_SUGGESTIONS_COUNT);
}
result = result.concat(suggestions[i]);
}
}
return result.slice(0, MAX_SUGGESTIONS_COUNT);
}
/**
* @param {string} ruleName
* @param {string[]} [suggestions=[]]
* @return {string}
*/
function rejectMessage(ruleName, suggestions = []) {
return `Unknown rule ${ruleName}.${
suggestions.length > 0 ? ` Did you mean ${suggestions.join(', ')}?` : ''
}`;
}
/** @type {Map<string, string[]>} */
const cache = new Map();
/**
* @param {string} unknownRuleName
* @param {import('postcss').Root} postcssRoot
* @param {import('stylelint').PostcssResult} postcssResult
* @returns {void}
*/
module.exports = function reportUnknownRuleNames(unknownRuleName, postcssRoot, postcssResult) {
const suggestions = cache.has(unknownRuleName)
? /** @type {string[]} */ (cache.get(unknownRuleName))
: extractSuggestions(unknownRuleName);
cache.set(unknownRuleName, suggestions);
postcssResult.warn(rejectMessage(unknownRuleName, suggestions), {
severity: 'error',
rule: unknownRuleName,
node: postcssRoot,
index: 0,
});
};
+110
View File
@@ -0,0 +1,110 @@
# alpha-value-notation
Specify percentage or number notation for alpha-values.
<!-- prettier-ignore -->
```css
a { color: rgb(0 0 0 / 0.5) }
/** ↑
* This notation */
```
The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix all of the problems reported by this rule.
## Options
`string`: `"number"|"percentage"`
### `"number"`
Alpha-values _must always_ use the number notation.
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a { opacity: 50% }
```
<!-- prettier-ignore -->
```css
a { color: rgb(0 0 0 / 50%) }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { opacity: 0.5 }
```
<!-- prettier-ignore -->
```css
a { color: rgb(0 0 0 / 0.5) }
```
### `"percentage"`
Alpha-values _must always_ use percentage notation.
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a { opacity: 0.5 }
```
<!-- prettier-ignore -->
```css
a { color: rgb(0 0 0 / 0.5) }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { opacity: 50% }
```
<!-- prettier-ignore -->
```css
a { color: rgb(0 0 0 / 50%) }
```
## Optional secondary options
### `exceptProperties: ["/regex/", /regex/, "string"]`
Reverse the primary option for matching properties.
For example with `"percentage"`.
Given:
```
["opacity"]
```
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a { opacity: 50% }
```
<!-- prettier-ignore -->
```css
a { color: rgb(0 0 0 / 0.5) }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { opacity: 0.5 }
```
<!-- prettier-ignore -->
```css
a { color: rgb(0 0 0 / 50%) }
```
+160
View File
@@ -0,0 +1,160 @@
// @ts-nocheck
'use strict';
const _ = require('lodash');
const valueParser = require('postcss-value-parser');
const declarationValueIndex = require('../../utils/declarationValueIndex');
const getDeclarationValue = require('../../utils/getDeclarationValue');
const isStandardSyntaxValue = require('../../utils/isStandardSyntaxValue');
const optionsMatches = require('../../utils/optionsMatches');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const setDeclarationValue = require('../../utils/setDeclarationValue');
const validateOptions = require('../../utils/validateOptions');
const ruleName = 'alpha-value-notation';
const messages = ruleMessages(ruleName, {
expected: (unfixed, fixed) => `Expected "${unfixed}" to be "${fixed}"`,
});
const ALPHA_PROPS = new Set(['opacity', 'shape-image-threshold']);
const ALPHA_FUNCS = new Set(['hsl', 'hsla', 'hwb', 'lab', 'lch', 'rgb', 'rgba']);
function rule(primary, options, context) {
return (root, result) => {
const validOptions = validateOptions(
result,
ruleName,
{
actual: primary,
possible: ['number', 'percentage'],
},
{
actual: options,
possible: {
exceptProperties: [_.isString, _.isRegExp],
},
optional: true,
},
);
if (!validOptions) return;
root.walkDecls((decl) => {
let needsFix = false;
const parsedValue = valueParser(getDeclarationValue(decl));
parsedValue.walk((node) => {
let alpha;
if (ALPHA_PROPS.has(decl.prop.toLowerCase())) {
alpha = findAlphaInValue(node);
} else {
if (node.type !== 'function') return;
if (!ALPHA_FUNCS.has(node.value.toLowerCase())) return;
alpha = findAlphaInFunction(node);
}
if (!alpha) return;
const { value } = alpha;
if (!isStandardSyntaxValue(value)) return;
if (!isNumber(value) && !isPercentage(value)) return;
const optionFuncs = {
number: {
expFunc: isNumber,
fixFunc: asNumber,
},
percentage: {
expFunc: isPercentage,
fixFunc: asPercentage,
},
};
let expectation = primary;
if (optionsMatches(options, 'exceptProperties', decl.prop)) {
expectation = Object.keys(optionFuncs).filter((key) => key !== expectation);
}
if (optionFuncs[expectation].expFunc(value)) return;
const fixed = optionFuncs[expectation].fixFunc(value);
const unfixed = value;
if (context.fix) {
alpha.value = fixed;
needsFix = true;
return;
}
report({
message: messages.expected(unfixed, fixed),
node: decl,
index: declarationValueIndex(decl) + alpha.sourceIndex,
result,
ruleName,
});
});
if (needsFix) {
setDeclarationValue(decl, parsedValue.toString());
}
});
};
}
function asPercentage(value) {
return `${Number((value * 100).toPrecision(3))}%`;
}
function asNumber(value) {
const { number } = valueParser.unit(value);
return Number((number / 100).toPrecision(3));
}
function findAlphaInValue(node) {
return node.type === 'word' || node.type === 'function' ? node : false;
}
function findAlphaInFunction(node) {
const args = node.nodes.filter(({ type }) => type === 'word' || type === 'function');
if (args.length === 4) return args[3];
const slashNodeIndex = node.nodes.findIndex(({ type, value }) => type === 'div' && value === '/');
if (slashNodeIndex !== -1) {
const nodesAfterSlash = node.nodes.slice(slashNodeIndex + 1, node.nodes.length);
return nodesAfterSlash.find(({ type }) => type === 'word');
}
return false;
}
function isPercentage(value) {
const { unit } = valueParser.unit(value);
return unit && unit === '%';
}
function isNumber(value) {
const { unit } = valueParser.unit(value);
return unit === '';
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
+65
View File
@@ -0,0 +1,65 @@
# at-rule-allowed-list
Specify a list of allowed at-rules.
<!-- prettier-ignore -->
```css
@keyframes name {}
/** ↑
* At-rules like this */
```
## Options
`array|string`: `["array", "of", "unprefixed", "at-rules"]|"at-rule"`
Given:
```
["extend", "keyframes"]
```
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
@import "path/to/file.css";
```
<!-- prettier-ignore -->
```css
@media screen and (max-width: 1024px) {
a { display: none; }
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { @extend placeholder; }
```
<!-- prettier-ignore -->
```css
@keyframes name {
from { top: 10px; }
to { top: 20px; }
}
```
<!-- prettier-ignore -->
```css
@KEYFRAMES name {
from { top: 10px; }
to { top: 20px; }
}
```
<!-- prettier-ignore -->
```css
@-moz-keyframes name {
from { top: 10px; }
to { top: 20px; }
}
```
+57
View File
@@ -0,0 +1,57 @@
// @ts-nocheck
'use strict';
const _ = require('lodash');
const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');
const vendor = require('../../utils/vendor');
const ruleName = 'at-rule-allowed-list';
const messages = ruleMessages(ruleName, {
rejected: (name) => `Unexpected at-rule "${name}"`,
});
function rule(listInput) {
// To allow for just a string as a parameter (not only arrays of strings)
const list = [].concat(listInput);
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: list,
possible: [_.isString],
});
if (!validOptions) {
return;
}
root.walkAtRules((atRule) => {
const name = atRule.name;
if (!isStandardSyntaxAtRule(atRule)) {
return;
}
if (list.includes(vendor.unprefixed(name).toLowerCase())) {
return;
}
report({
message: messages.rejected(name),
node: atRule,
result,
ruleName,
});
});
};
}
rule.primaryOptionArray = true;
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
+52
View File
@@ -0,0 +1,52 @@
# at-rule-blacklist
**_Deprecated: Instead use the [`at-rule-disallowed-list`](../at-rule-disallowed-list/README.md) rule._**
Specify a list of disallowed at-rules.
<!-- prettier-ignore -->
```css
@keyframes name {}
/** ↑
* At-rules like this */
```
## Options
`array|string`: `["array", "of", "unprefixed", "at-rules"]|"at-rule"`
Given:
```
["extend", "keyframes"]
```
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a { @extend placeholder; }
```
<!-- prettier-ignore -->
```css
@keyframes name {
from { top: 10px; }
to { top: 20px; }
}
```
<!-- prettier-ignore -->
```css
@-moz-keyframes name {
from { top: 10px; }
to { top: 20px; }
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@import "path/to/file.css";
```
+64
View File
@@ -0,0 +1,64 @@
// @ts-nocheck
'use strict';
const _ = require('lodash');
const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');
const vendor = require('../../utils/vendor');
const ruleName = 'at-rule-blacklist';
const messages = ruleMessages(ruleName, {
rejected: (name) => `Unexpected at-rule "${name}"`,
});
function rule(listInput) {
// To allow for just a string as a parameter (not only arrays of strings)
const list = [].concat(listInput);
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: list,
possible: [_.isString],
});
if (!validOptions) {
return;
}
result.warn(`'${ruleName}' has been deprecated. Instead use 'at-rule-disallowed-list'.`, {
stylelintType: 'deprecation',
stylelintReference: `https://github.com/stylelint/stylelint/blob/13.7.0/lib/rules/${ruleName}/README.md`,
});
root.walkAtRules((atRule) => {
const name = atRule.name;
if (!isStandardSyntaxAtRule(atRule)) {
return;
}
if (!list.includes(vendor.unprefixed(name).toLowerCase())) {
return;
}
report({
message: messages.rejected(name),
node: atRule,
result,
ruleName,
});
});
};
}
rule.primaryOptionArray = true;
rule.ruleName = ruleName;
rule.messages = messages;
rule.meta = { deprecated: true };
module.exports = rule;
+50
View File
@@ -0,0 +1,50 @@
# at-rule-disallowed-list
Specify a list of disallowed at-rules.
<!-- prettier-ignore -->
```css
@keyframes name {}
/** ↑
* At-rules like this */
```
## Options
`array|string`: `["array", "of", "unprefixed", "at-rules"]|"at-rule"`
Given:
```
["extend", "keyframes"]
```
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a { @extend placeholder; }
```
<!-- prettier-ignore -->
```css
@keyframes name {
from { top: 10px; }
to { top: 20px; }
}
```
<!-- prettier-ignore -->
```css
@-moz-keyframes name {
from { top: 10px; }
to { top: 20px; }
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@import "path/to/file.css";
```
+57
View File
@@ -0,0 +1,57 @@
// @ts-nocheck
'use strict';
const _ = require('lodash');
const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');
const vendor = require('../../utils/vendor');
const ruleName = 'at-rule-disallowed-list';
const messages = ruleMessages(ruleName, {
rejected: (name) => `Unexpected at-rule "${name}"`,
});
function rule(listInput) {
// To allow for just a string as a parameter (not only arrays of strings)
const list = [].concat(listInput);
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: list,
possible: [_.isString],
});
if (!validOptions) {
return;
}
root.walkAtRules((atRule) => {
const name = atRule.name;
if (!isStandardSyntaxAtRule(atRule)) {
return;
}
if (!list.includes(vendor.unprefixed(name).toLowerCase())) {
return;
}
report({
message: messages.rejected(name),
node: atRule,
result,
ruleName,
});
});
};
}
rule.primaryOptionArray = true;
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
+421
View File
@@ -0,0 +1,421 @@
# at-rule-empty-line-before
Require or disallow an empty line before at-rules.
<!-- prettier-ignore -->
```css
a {}
/* ← */
@media {} /* ↑ */
/** ↑
* This line */
```
This rule ignores:
- at-rules that are the very first node in the source
- `@import` in Less.
The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix all of the problems reported by this rule. We recommend to enable [`indentation`](../indentation/README.md) rule for better autofixing results with this rule.
## Options
`string`: `"always"|"never"`
### `"always"`
There _must always_ be an empty line before at-rules.
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a {} @media {}
```
<!-- prettier-ignore -->
```css
a {}
@media {}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a {}
@media {}
```
### `"never"`
There _must never_ be an empty line before at-rules.
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a {}
@media {}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a {} @media {}
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
@charset "UTF-8";
@import url(x.css);
@import url(y.css);
@media (min-width: 100px) {}
@media (min-width: 200px) {}
```
<!-- prettier-ignore -->
```css
a {
@extends .foo;
@extends .bar;
@include x;
@include y {}
}
```
#### `"inside-block"`
Reverse the primary option for at-rules that are inside a block.
For example, with `"always"`:
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a {
@extend foo;
color: pink;
}
b {
color: pink;
@extend foo;
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```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.
Shared-line comments do not affect this option.
For example, with `"always"`:
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@charset "UTF-8";
@import url(x.css);
@import url(y.css);
```
<!-- prettier-ignore -->
```css
@charset "UTF-8";
@import url(x.css); /* comment */
@import url(y.css);
```
<!-- prettier-ignore -->
```css
a {
@extends .foo;
@extends .bar;
@include loop;
@include doo;
}
```
#### `"blockless-after-blockless"`
Reverse the primary option for blockless at-rules that follow another blockless at-rule.
Shared-line comments do not affect this option.
For example, with `"always"`:
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
@import url(x.css);
@import url(y.css);
@media print {}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@import url(x.css);
@import url(y.css);
@media print {}
```
<!-- prettier-ignore -->
```css
@import url(x.css); /* comment */
@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 violations:
<!-- prettier-ignore -->
```css
a {
@extend foo;
color: pink;
}
b {
color: pink;
@extend foo;
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a {
@extend foo;
color: pink;
}
b {
color: pink;
@extend foo;
}
```
### `ignore: ["after-comment", "first-nested", "inside-block", "blockless-after-same-name-blockless", "blockless-after-blockless"]`
#### `"after-comment"`
Ignore at-rules that follow a comment.
Shared-line comments do not trigger this option.
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
/* comment */
@media {}
```
<!-- prettier-ignore -->
```css
/* comment */
@media {}
```
<!-- prettier-ignore -->
```css
@media {} /* comment */
@media {}
```
#### `"first-nested"`
Ignore at-rules that are nested and the first child of their parent node.
For example, with `"always"`:
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@supports {
@media {}
@media {}
}
```
#### `"inside-block"`
Ignore at-rules that are inside a block.
For example, with `"always"`:
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
@charset "UTF-8";
@import url(x.css);
@import url(y.css);
```
<!-- prettier-ignore -->
```css
a {
@extends .foo;
@extends .bar;
@include loop;
@include doo;
}
```
#### `"blockless-after-blockless"`
Ignore blockless at-rules that follow another blockless at-rule.
For example, with `"always"`:
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@import url(x.css);
@import url(y.css);
@media print {}
```
<!-- prettier-ignore -->
```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:
```
["import"]
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@charset "UTF-8";
@import {}
```
+161
View File
@@ -0,0 +1,161 @@
// @ts-nocheck
'use strict';
const _ = require('lodash');
const addEmptyLineBefore = require('../../utils/addEmptyLineBefore');
const getPreviousNonSharedLineCommentNode = require('../../utils/getPreviousNonSharedLineCommentNode');
const hasEmptyLine = require('../../utils/hasEmptyLine');
const isAfterComment = require('../../utils/isAfterComment');
const isBlocklessAtRuleAfterBlocklessAtRule = require('../../utils/isBlocklessAtRuleAfterBlocklessAtRule');
const isBlocklessAtRuleAfterSameNameBlocklessAtRule = require('../../utils/isBlocklessAtRuleAfterSameNameBlocklessAtRule');
const isFirstNested = require('../../utils/isFirstNested');
const isFirstNodeOfRoot = require('../../utils/isFirstNodeOfRoot');
const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule');
const optionsMatches = require('../../utils/optionsMatches');
const removeEmptyLinesBefore = require('../../utils/removeEmptyLinesBefore');
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',
});
function rule(expectation, options, context) {
return (root, result) => {
const validOptions = validateOptions(
result,
ruleName,
{
actual: expectation,
possible: ['always', 'never'],
},
{
actual: options,
possible: {
except: [
'after-same-name',
'inside-block',
'blockless-after-same-name-blockless',
'blockless-after-blockless',
'first-nested',
],
ignore: [
'after-comment',
'first-nested',
'inside-block',
'blockless-after-same-name-blockless',
'blockless-after-blockless',
],
ignoreAtRules: [_.isString],
},
optional: true,
},
);
if (!validOptions) {
return;
}
root.walkAtRules((atRule) => {
const isNested = atRule.parent.type !== 'root';
// Ignore the first node
if (isFirstNodeOfRoot(atRule)) {
return;
}
if (!isStandardSyntaxAtRule(atRule)) {
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-after-blockless') &&
isBlocklessAtRuleAfterBlocklessAtRule(atRule)
) {
return;
}
// Optionally ignore the node if it is the first nested
if (optionsMatches(options, 'ignore', 'first-nested') && isFirstNested(atRule)) {
return;
}
// Optionally ignore the expectation if the node is blockless
// and following another blockless at-rule with the same name
if (
optionsMatches(options, 'ignore', 'blockless-after-same-name-blockless') &&
isBlocklessAtRuleAfterSameNameBlocklessAtRule(atRule)
) {
return;
}
// Optionally ignore the expectation if the node is inside a block
if (optionsMatches(options, 'ignore', 'inside-block') && isNested) {
return;
}
// Optionally ignore the expectation if a comment precedes this node
if (optionsMatches(options, 'ignore', 'after-comment') && isAfterComment(atRule)) {
return;
}
const hasEmptyLineBefore = hasEmptyLine(atRule.raws.before);
let expectEmptyLineBefore = expectation === 'always';
// Optionally reverse the expectation if any exceptions apply
if (
(optionsMatches(options, 'except', 'after-same-name') &&
isAtRuleAfterSameNameAtRule(atRule)) ||
(optionsMatches(options, 'except', 'inside-block') && isNested) ||
(optionsMatches(options, 'except', 'first-nested') && isFirstNested(atRule)) ||
(optionsMatches(options, 'except', 'blockless-after-blockless') &&
isBlocklessAtRuleAfterBlocklessAtRule(atRule)) ||
(optionsMatches(options, 'except', 'blockless-after-same-name-blockless') &&
isBlocklessAtRuleAfterSameNameBlocklessAtRule(atRule))
) {
expectEmptyLineBefore = !expectEmptyLineBefore;
}
// Return if the expectation is met
if (expectEmptyLineBefore === hasEmptyLineBefore) {
return;
}
// Fix
if (context.fix) {
if (expectEmptyLineBefore) {
addEmptyLineBefore(atRule, context.newline);
} else {
removeEmptyLinesBefore(atRule, context.newline);
}
return;
}
const message = expectEmptyLineBefore ? messages.expected : messages.rejected;
report({ message, node: atRule, result, ruleName });
});
};
}
function isAtRuleAfterSameNameAtRule(atRule) {
const previousNode = getPreviousNonSharedLineCommentNode(atRule);
return previousNode && previousNode.type === 'atrule' && previousNode.name === atRule.name;
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
+110
View File
@@ -0,0 +1,110 @@
# at-rule-name-case
Specify lowercase or uppercase for at-rules names.
<!-- prettier-ignore -->
```css
@media (min-width: 10px) {}
/** ↑
* This at-rule name */
```
Only lowercase at-rule names are valid in SCSS.
The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix some of the problems reported by this rule.
## Options
`string`: `"lower"|"upper"`
### `"lower"`
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
@Charset 'UTF-8';
```
<!-- prettier-ignore -->
```css
@cHarSeT 'UTF-8';
```
<!-- prettier-ignore -->
```css
@CHARSET 'UTF-8';
```
<!-- prettier-ignore -->
```css
@Media (min-width: 50em) {}
```
<!-- prettier-ignore -->
```css
@mEdIa (min-width: 50em) {}
```
<!-- prettier-ignore -->
```css
@MEDIA (min-width: 50em) {}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@charset 'UTF-8';
```
<!-- prettier-ignore -->
```css
@media (min-width: 50em) {}
```
### `"upper"`
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
@Charset 'UTF-8';
```
<!-- prettier-ignore -->
```css
@cHarSeT 'UTF-8';
```
<!-- prettier-ignore -->
```css
@charset 'UTF-8';
```
<!-- prettier-ignore -->
```css
@Media (min-width: 50em) {}
```
<!-- prettier-ignore -->
```css
@mEdIa (min-width: 50em) {}
```
<!-- prettier-ignore -->
```css
@media (min-width: 50em) {}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@CHARSET 'UTF-8';
```
<!-- prettier-ignore -->
```css
@MEDIA (min-width: 50em) {}
```
+58
View File
@@ -0,0 +1,58 @@
// @ts-nocheck
'use strict';
const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule');
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}"`,
});
function rule(expectation, options, context) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: ['lower', 'upper'],
});
if (!validOptions) {
return;
}
root.walkAtRules((atRule) => {
if (!isStandardSyntaxAtRule(atRule)) {
return;
}
const name = atRule.name;
const expectedName = expectation === 'lower' ? name.toLowerCase() : name.toUpperCase();
if (name === expectedName) {
return;
}
if (context.fix) {
atRule.name = expectedName;
return;
}
report({
message: messages.expected(name, expectedName),
node: atRule,
ruleName,
result,
});
});
};
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
+108
View File
@@ -0,0 +1,108 @@
# at-rule-name-newline-after
Require a newline after at-rule names.
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
@charset "UTF-8";
```
<!-- prettier-ignore -->
```css
@media (min-width: 700px) and
(orientation: landscape) {}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@charset
"UTF-8";
```
<!-- prettier-ignore -->
```css
@import
"x.css" screen and
(orientation:landscape);
```
<!-- prettier-ignore -->
```css
@media
(min-width: 700px) and (orientation: landscape) {}
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
@import "x.css" screen and
(orientation:landscape);
```
<!-- prettier-ignore -->
```css
@media (min-width: 700px) and
(orientation: landscape) {}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@charset "UTF-8";
```
<!-- prettier-ignore -->
```css
@charset
"UTF-8";
```
<!-- prettier-ignore -->
```css
@import "x.css" screen and (orientation:landscape);
```
<!-- prettier-ignore -->
```css
@media (min-width: 700px) and (orientation: landscape) {}
```
<!-- prettier-ignore -->
```css
@media
(min-width: 700px) and
(orientation: landscape) {}
```
+40
View File
@@ -0,0 +1,40 @@
// @ts-nocheck
'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}"`,
});
function rule(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;
+117
View File
@@ -0,0 +1,117 @@
# at-rule-name-space-after
Require a single space after at-rule names.
<!-- prettier-ignore -->
```css
@media (max-width: 600px) {}
/** ↑
* The space after at-rule names */
```
The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix all of the problems reported by this rule.
## Options
`string`: `"always"|"always-single-line"`
### `"always"`
There _must always_ be a single space after at-rule names.
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
@charset"UTF-8";
```
<!-- prettier-ignore -->
```css
@media(min-width: 700px) {}
```
<!-- prettier-ignore -->
```css
@media (min-width: 700px) {}
```
<!-- prettier-ignore -->
```css
@media
(min-width: 700px) {}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@charset "UTF-8";
```
<!-- prettier-ignore -->
```css
@import url("x.css");
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
@charset"UTF-8";
```
<!-- prettier-ignore -->
```css
@media(min-width: 700px) {}
```
<!-- prettier-ignore -->
```css
@media (min-width: 700px) {}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@charset "UTF-8";
```
<!-- prettier-ignore -->
```css
@import url("x.css");
```
<!-- prettier-ignore -->
```css
@media (min-width: 700px) {}
```
<!-- prettier-ignore -->
```css
@media
(min-width: 700px) {}
```
<!-- prettier-ignore -->
```css
@media(min-width: 700px) and
(orientation: portrait) {}
```
<!-- prettier-ignore -->
```css
@media
(min-width: 700px) and
(orientation: portrait) {}
```
+45
View File
@@ -0,0 +1,45 @@
// @ts-nocheck
'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}"`,
});
function rule(expectation, options, context) {
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,
fix: context.fix
? (atRule) => {
atRule.raws.afterName = atRule.raws.afterName.replace(/^\s*/, ' ');
}
: null,
});
};
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
+74
View File
@@ -0,0 +1,74 @@
# at-rule-no-unknown
Disallow unknown at-rules.
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
@unknown {}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@charset "UTF-8";
```
<!-- prettier-ignore -->
```css
@CHARSET "UTF-8";
```
<!-- prettier-ignore -->
```css
@media (max-width: 960px) {}
```
<!-- prettier-ignore -->
```css
@font-feature-values Font One {
@styleset {}
}
```
## Optional secondary options
### `ignoreAtRules: ["/regex/", /regex/, "string"]`
Given:
```
["/^my-/", "custom"]
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@my-at-rule "x.css";
```
<!-- prettier-ignore -->
```css
@my-other-at-rule {}
```
<!-- prettier-ignore -->
```css
@custom {}
```
+67
View File
@@ -0,0 +1,67 @@
// @ts-nocheck
'use strict';
const _ = require('lodash');
const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule');
const keywordSets = require('../../reference/keywordSets');
const optionsMatches = require('../../utils/optionsMatches');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');
const vendor = require('../../utils/vendor');
const ruleName = 'at-rule-no-unknown';
const messages = ruleMessages(ruleName, {
rejected: (atRule) => `Unexpected unknown at-rule "${atRule}"`,
});
function rule(actual, options) {
return (root, result) => {
const validOptions = validateOptions(
result,
ruleName,
{ actual },
{
actual: options,
possible: {
ignoreAtRules: [_.isString, _.isRegExp],
},
optional: true,
},
);
if (!validOptions) {
return;
}
root.walkAtRules((atRule) => {
if (!isStandardSyntaxAtRule(atRule)) {
return;
}
const name = atRule.name;
// Return early if at-rule is to be ignored
if (optionsMatches(options, 'ignoreAtRules', atRule.name)) {
return;
}
if (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;
+40
View File
@@ -0,0 +1,40 @@
# at-rule-no-vendor-prefix
Disallow vendor prefixes for at-rules.
<!-- prettier-ignore -->
```css
@-webkit-keyframes { 0% { top: 0; } }
/** ↑
* This prefix */
```
The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix all of the problems reported by this rule.
## Options
### `true`
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
@-webkit-keyframes { 0% { top: 0; } }
```
<!-- prettier-ignore -->
```css
@-ms-viewport { orientation: landscape; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@keyframes { 0% { top: 0; } }
```
<!-- prettier-ignore -->
```css
@viewport { orientation: landscape; }
```
+58
View File
@@ -0,0 +1,58 @@
// @ts-nocheck
'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}"`,
});
function rule(actual, options, context) {
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.startsWith('-')) {
return;
}
if (!isAutoprefixable.atRuleName(name)) {
return;
}
if (context.fix) {
atRule.name = isAutoprefixable.unprefix(atRule.name);
return;
}
report({
message: messages.rejected(name),
node: atRule,
result,
ruleName,
});
});
};
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
@@ -0,0 +1,53 @@
# at-rule-property-required-list
Specify a list of required properties for an at-rule.
<!-- prettier-ignore -->
```css
@font-face { font-display: swap; font-family: 'foo'; }
/** ↑ ↑ ↑
* At-rule and required property names */
```
## Options
`object`: `{ "at-rule-name": ["array", "of", "properties"] }`
Given:
```
{
"font-face": ["font-display", "font-family", "font-style"]
}
```
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
@font-face {
font-family: 'foo';
src: url('./fonts/foo.woff2') format('woff2');
}
```
<!-- prettier-ignore -->
```css
@font-face {
font-family: 'foo';
font-style: normal;
src: url('./fonts/foo.woff2') format('woff2');
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@font-face {
font-display: swap;
font-family: 'foo';
font-style: normal;
src: url('./fonts/foo.woff2') format('woff2');
}
```
@@ -0,0 +1,65 @@
// @ts-nocheck
'use strict';
const _ = require('lodash');
const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');
const ruleName = 'at-rule-property-required-list';
const messages = ruleMessages(ruleName, {
expected: (property, atRule) => `Expected property "${property}" for at-rule "${atRule}"`,
});
function rule(list) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: list,
possible: [_.isObject],
});
if (!validOptions) {
return;
}
root.walkAtRules((atRule) => {
if (!isStandardSyntaxAtRule(atRule)) {
return;
}
const { name, nodes } = atRule;
const atRuleName = name.toLowerCase();
if (!list[atRuleName]) {
return;
}
list[atRuleName].forEach((property) => {
const propertyName = property.toLowerCase();
const hasProperty = nodes.find(
({ type, prop }) => type === 'decl' && prop.toLowerCase() === propertyName,
);
if (hasProperty) {
return;
}
return report({
message: messages.expected(propertyName, atRuleName),
node: atRule,
result,
ruleName,
});
});
});
};
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
@@ -0,0 +1,55 @@
# at-rule-property-requirelist
**_Deprecated: Instead use the [`at-rule-property-required-list`](../at-rule-property-required-list/README.md) rule._**
Specify a list of required properties for an at-rule.
<!-- prettier-ignore -->
```css
@font-face { font-display: swap; font-family: 'foo'; }
/** ↑ ↑ ↑
* At-rule and required property names */
```
## Options
`object`: `{ "at-rule-name": ["array", "of", "properties"] }`
Given:
```
{
"font-face": ["font-display", "font-family", "font-style"]
}
```
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
@font-face {
font-family: 'foo';
src: url('./fonts/foo.woff2') format('woff2');
}
```
<!-- prettier-ignore -->
```css
@font-face {
font-family: 'foo';
font-style: normal;
src: url('./fonts/foo.woff2') format('woff2');
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@font-face {
font-display: swap;
font-family: 'foo';
font-style: normal;
src: url('./fonts/foo.woff2') format('woff2');
}
```
+74
View File
@@ -0,0 +1,74 @@
// @ts-nocheck
'use strict';
const _ = require('lodash');
const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');
const ruleName = 'at-rule-property-requirelist';
const messages = ruleMessages(ruleName, {
expected: (property, atRule) => `Expected property "${property}" for at-rule "${atRule}"`,
});
function rule(list) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: list,
possible: [_.isObject],
});
if (!validOptions) {
return;
}
result.warn(
`'${ruleName}' has been deprecated. Instead use 'at-rule-property-required-list'.`,
{
stylelintType: 'deprecation',
stylelintReference: `https://github.com/stylelint/stylelint/blob/13.7.0/lib/rules/${ruleName}/README.md`,
},
);
root.walkAtRules((atRule) => {
if (!isStandardSyntaxAtRule(atRule)) {
return;
}
const { name, nodes } = atRule;
const atRuleName = name.toLowerCase();
if (!list[atRuleName]) {
return;
}
list[atRuleName].forEach((property) => {
const propertyName = property.toLowerCase();
const hasProperty = nodes.find(
({ type, prop }) => type === 'decl' && prop.toLowerCase() === propertyName,
);
if (hasProperty) {
return;
}
return report({
message: messages.expected(propertyName, atRuleName),
node: atRule,
result,
ruleName,
});
});
});
};
}
rule.ruleName = ruleName;
rule.messages = messages;
rule.meta = { deprecated: true };
module.exports = rule;
@@ -0,0 +1,63 @@
# at-rule-semicolon-newline-after
Require a newline after the semicolon of at-rules.
<!-- prettier-ignore -->
```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:
<!-- prettier-ignore -->
```css
@import url("x.css"); /* end-of-line comment */
a {}
```
The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix all of the problems reported by this rule.
## Options
`string`: `"always"`
### `"always"`
There _must always_ be a newline after the semicolon.
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
@import url("x.css"); @import url("y.css");
```
<!-- prettier-ignore -->
```css
@import url("x.css"); a {}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@import url("x.css");
@import url("y.css");
```
<!-- prettier-ignore -->
```css
@import url("x.css"); /* end-of-line comment */
a {}
```
<!-- prettier-ignore -->
```css
@import url("x.css");
a {}
```
@@ -0,0 +1,78 @@
// @ts-nocheck
'use strict';
const hasBlock = require('../../utils/hasBlock');
const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule');
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 ";"',
});
function rule(actual, secondary, context) {
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;
}
if (!isStandardSyntaxAtRule(atRule)) {
return;
}
// Allow an end-of-line comment
const nodeToCheck = nextNonCommentNode(nextNode);
if (!nodeToCheck) {
return;
}
checker.afterOneOnly({
source: rawNodeString(nodeToCheck),
index: -1,
err: (msg) => {
if (context.fix) {
nodeToCheck.raws.before = context.newline + nodeToCheck.raws.before;
} else {
report({
message: msg,
node: atRule,
index: atRule.toString().length + 1,
result,
ruleName,
});
}
},
});
});
};
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
@@ -0,0 +1,50 @@
# at-rule-semicolon-space-before
Require a single space or disallow whitespace before the semicolons of at-rules.
<!-- prettier-ignore -->
```css
@import "components/buttons";
/** ↑
* The space before this semicolon */
```
## Options
`string`: `"always"|"never"`
### `"always"`
There _must always_ be a single space before the semicolons.
The following pattern is considered a violation:
<!-- prettier-ignore -->
```css
@import "components/buttons";
```
The following pattern is _not_ considered a violation:
<!-- prettier-ignore -->
```css
@import "components/buttons" ;
```
### `"never"`
There _must never_ be a single space before the semicolons.
The following pattern is considered a violation:
<!-- prettier-ignore -->
```css
@import "components/buttons" ;
```
The following pattern is _not_ considered a violation:
<!-- prettier-ignore -->
```css
@import "components/buttons";
```
@@ -0,0 +1,63 @@
// @ts-nocheck
'use strict';
const hasBlock = require('../../utils/hasBlock');
const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule');
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-space-before';
const messages = ruleMessages(ruleName, {
expectedBefore: () => 'Expected single space before ";"',
rejectedBefore: () => 'Unexpected whitespace before ";"',
});
function rule(expectation) {
const checker = whitespaceChecker('space', expectation, messages);
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: ['always', 'never'],
});
if (!validOptions) {
return;
}
root.walkAtRules((atRule) => {
if (hasBlock(atRule)) {
return;
}
if (!isStandardSyntaxAtRule(atRule)) {
return;
}
const nodeString = rawNodeString(atRule);
checker.before({
source: nodeString,
index: nodeString.length,
err: (m) => {
report({
message: m,
node: atRule,
index: nodeString.length - 1,
result,
ruleName,
});
},
});
});
};
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
+67
View File
@@ -0,0 +1,67 @@
# at-rule-whitelist
**_Deprecated: Instead use the [`at-rule-allowed-list`](../at-rule-allowed-list/README.md) rule._**
Specify a list of allowed at-rules.
<!-- prettier-ignore -->
```css
@keyframes name {}
/** ↑
* At-rules like this */
```
## Options
`array|string`: `["array", "of", "unprefixed", "at-rules"]|"at-rule"`
Given:
```
["extend", "keyframes"]
```
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
@import "path/to/file.css";
```
<!-- prettier-ignore -->
```css
@media screen and (max-width: 1024px) {
a { display: none; }
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { @extend placeholder; }
```
<!-- prettier-ignore -->
```css
@keyframes name {
from { top: 10px; }
to { top: 20px; }
}
```
<!-- prettier-ignore -->
```css
@KEYFRAMES name {
from { top: 10px; }
to { top: 20px; }
}
```
<!-- prettier-ignore -->
```css
@-moz-keyframes name {
from { top: 10px; }
to { top: 20px; }
}
```
+64
View File
@@ -0,0 +1,64 @@
// @ts-nocheck
'use strict';
const _ = require('lodash');
const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');
const vendor = require('../../utils/vendor');
const ruleName = 'at-rule-whitelist';
const messages = ruleMessages(ruleName, {
rejected: (name) => `Unexpected at-rule "${name}"`,
});
function rule(listInput) {
// To allow for just a string as a parameter (not only arrays of strings)
const list = [].concat(listInput);
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: list,
possible: [_.isString],
});
if (!validOptions) {
return;
}
result.warn(`'${ruleName}' has been deprecated. Instead use 'at-rule-allowed-list'.`, {
stylelintType: 'deprecation',
stylelintReference: `https://github.com/stylelint/stylelint/blob/13.7.0/lib/rules/${ruleName}/README.md`,
});
root.walkAtRules((atRule) => {
const name = atRule.name;
if (!isStandardSyntaxAtRule(atRule)) {
return;
}
if (list.includes(vendor.unprefixed(name).toLowerCase())) {
return;
}
report({
message: messages.rejected(name),
node: atRule,
result,
ruleName,
});
});
};
}
rule.primaryOptionArray = true;
rule.ruleName = ruleName;
rule.messages = messages;
rule.meta = { deprecated: true };
module.exports = rule;
+43
View File
@@ -0,0 +1,43 @@
// @ts-nocheck
'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) => {
if (options.fix) {
options.fix(node);
return;
}
report({
message: m,
node,
index,
result: options.result,
ruleName: options.checkedRuleName,
});
},
errTarget: `@${node.name}`,
});
}
};
@@ -0,0 +1,238 @@
# block-closing-brace-empty-line-before
Require or disallow an empty line before the closing brace of blocks.
<!-- prettier-ignore -->
```css
a {
color: pink;
/* ← */
} /* ↑ */
/** ↑
* This line */
```
The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix all of the problems reported by this rule.
## Options
`string`: `"always-multi-line"|"never"`
### `always-multi-line`
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a {
color: pink;
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a {
color: pink;
}
```
<!-- prettier-ignore -->
```css
a { color: pink; }
```
### `never`
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a {
color: pink;
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a {
color: pink;
}
```
<!-- prettier-ignore -->
```css
a { color: pink; }
```
## Optional secondary options
### `except: ["after-closing-brace"]`
When a rule is nested, `after-closing-brace` brace will reverse the primary option.
For example, with `"never"` and `except: ["after-closing-brace"]`:
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
@media print {
a {
color: aquamarine;
}
}
```
<!-- prettier-ignore -->
```css
@supports (animation-name: test) {
a {
color: aquamarine;
}
}
```
<!-- prettier-ignore -->
```css
@keyframes test {
100% {
color: aquamarine;
}
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@media print {
a {
color: aquamarine;
}
}
```
<!-- prettier-ignore -->
```css
@font-face {
font-family: "MyFont";
src: url("myfont.woff2") format("woff2");
}
```
<!-- prettier-ignore -->
```css
@supports (animation-name: test) {
a {
color: aquamarine;
}
}
```
<!-- prettier-ignore -->
```css
@keyframes test {
100% {
color: aquamarine;
}
}
```
For example, with `"always-multi-line"` and `except: ["after-closing-brace"]`:
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
@media print {
a {
color: aquamarine;
}
}
```
<!-- prettier-ignore -->
```css
@supports (animation-name: test) {
a {
color: aquamarine;
}
}
```
<!-- prettier-ignore -->
```css
@keyframes test {
100% {
color: aquamarine;
}
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@media print {
a {
color: aquamarine;
}
}
```
<!-- prettier-ignore -->
```css
@font-face {
font-family: "MyFont";
src: url("myfont.woff2") format("woff2");
}
```
<!-- prettier-ignore -->
```css
@supports (animation-name: test) {
a {
color: aquamarine;
}
}
```
<!-- prettier-ignore -->
```css
@keyframes test {
100% {
color: aquamarine;
}
}
```
@@ -0,0 +1,118 @@
// @ts-nocheck
'use strict';
const addEmptyLineAfter = require('../../utils/addEmptyLineAfter');
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 optionsMatches = require('../../utils/optionsMatches');
const removeEmptyLineAfter = require('../../utils/removeEmptyLinesAfter');
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',
});
function rule(expectation, options, context) {
return (root, result) => {
const validOptions = validateOptions(
result,
ruleName,
{
actual: expectation,
possible: ['always-multi-line', 'never'],
},
{
actual: options,
possible: {
except: ['after-closing-brace'],
},
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 empty block
if (!hasBlock(statement) || hasEmptyBlock(statement)) {
return;
}
// Get whitespace after ""}", ignoring extra semicolon
const before = (statement.raws.after || '').replace(/;+/, '');
// Calculate index
const statementString = statement.toString();
let index = statementString.length - 1;
if (statementString[index - 1] === '\r') {
index -= 1;
}
// Set expectation
const expectEmptyLineBefore = (() => {
const childNodeTypes = statement.nodes.map((item) => item.type);
// Reverse the primary options if `after-closing-brace` is set
if (
optionsMatches(options, 'except', 'after-closing-brace') &&
statement.type === 'atrule' &&
!childNodeTypes.includes('decl')
) {
return expectation === 'never';
}
return Boolean(
expectation === 'always-multi-line' && !isSingleLineString(blockString(statement)),
);
})();
// Check for at least one empty line
const hasEmptyLineBefore = hasEmptyLine(before);
// Return if the expectation is met
if (expectEmptyLineBefore === hasEmptyLineBefore) {
return;
}
if (context.fix) {
if (expectEmptyLineBefore) {
addEmptyLineAfter(statement, context.newline);
} else {
removeEmptyLineAfter(statement, context.newline);
}
return;
}
const message = expectEmptyLineBefore ? messages.expected : messages.rejected;
report({
message,
result,
ruleName,
node: statement,
index,
});
}
};
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
@@ -0,0 +1,196 @@
# block-closing-brace-newline-after
Require a newline or disallow whitespace after the closing brace of blocks.
<!-- prettier-ignore -->
```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,
<!-- prettier-ignore -->
```css
a {
color: pink;
} /* end-of-line comment */
```
This rule allows a trailing semicolon after the closing brace of a block. For example,
<!-- prettier-ignore -->
```css
:root {
--toolbar-theme: {
background-color: hsl(120, 70%, 95%);
};
/* ↑
* This semicolon */
}
```
The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix all of the problems reported by this rule.
## 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 violations:
<!-- prettier-ignore -->
```css
a { color: pink; }b { color: red; }
```
<!-- prettier-ignore -->
```css
a { color: pink;
} b { color: red; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a { color: pink; } b { color: red; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink;
} b { color: red; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a { color: pink; } b { color: red; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }b { color: red; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a { color: pink;
}b { color: red; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }b { color: red; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a { color: pink;
} b { color: red; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; } b { color: red; }
```
<!-- prettier-ignore -->
```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:
```
["if", "else"]
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@if ($var) {
color: pink;
} @else if ($var2) {
color: red;
} @else {
color: blue;
}
```
<!-- prettier-ignore -->
```css
@if ($var) { color: pink; } @else { color: blue; }
```
@@ -0,0 +1,137 @@
// @ts-nocheck
'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',
});
function rule(expectation, options, context) {
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().includes('\n');
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.startsWith(';')) {
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) => {
if (context.fix) {
if (expectation.startsWith('always')) {
const index = nodeToCheck.raws.before.search(/\r?\n/);
if (index >= 0) {
nodeToCheck.raws.before = nodeToCheck.raws.before.slice(index);
} else {
nodeToCheck.raws.before = context.newline + nodeToCheck.raws.before;
}
return;
}
if (expectation.startsWith('never')) {
nodeToCheck.raws.before = '';
return;
}
}
report({
message: msg,
node: statement,
index: reportIndex,
result,
ruleName,
});
},
});
}
};
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
@@ -0,0 +1,93 @@
# block-closing-brace-newline-before
Require a newline or disallow whitespace before the closing brace of blocks.
<!-- prettier-ignore -->
```css
a { color: pink;
}
/** ↑
* The newline before this brace */
```
The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix all of the problems reported by this rule.
## 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 violations:
<!-- prettier-ignore -->
```css
a { color: pink;}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink;
}
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a {
color: pink;}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a {
color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```css
a {
color: pink;}
```
@@ -0,0 +1,117 @@
// @ts-nocheck
'use strict';
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',
});
function rule(expectation, options, context) {
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 (!after.startsWith('\n') && !after.startsWith('\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) {
if (context.fix) {
if (expectation.startsWith('always')) {
const firstWhitespaceIndex = statement.raws.after.search(/\s/);
const newlineBefore =
firstWhitespaceIndex >= 0
? statement.raws.after.slice(0, firstWhitespaceIndex)
: statement.raws.after;
const newlineAfter =
firstWhitespaceIndex >= 0 ? statement.raws.after.slice(firstWhitespaceIndex) : '';
const newlineIndex = newlineAfter.search(/\r?\n/);
if (newlineIndex >= 0) {
statement.raws.after = newlineBefore + newlineAfter.slice(newlineIndex);
} else {
statement.raws.after = newlineBefore + context.newline + newlineAfter;
}
return;
}
if (expectation === 'never-multi-line') {
statement.raws.after = statement.raws.after.replace(/\s/g, '');
return;
}
}
report({
message,
result,
ruleName,
node: statement,
index,
});
}
}
};
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
@@ -0,0 +1,179 @@
# block-closing-brace-space-after
Require a single space or disallow whitespace after the closing brace of blocks.
<!-- prettier-ignore -->
```css
a { color: pink; }
/** ↑
* The space after this brace */
```
This rule allows a trailing semicolon after the closing brace of a block. For example,
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a { color: pink; }b { color: red; }
```
<!-- prettier-ignore -->
```css
a { color: pink; }
b { color: red; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; } b { color: red; }
```
### `"never"`
There _must never_ be whitespace after the closing brace.
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; } b { color: red; }
```
<!-- prettier-ignore -->
```css
a { color: pink; }
b { color: red; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }b { color: red; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a { color: pink; }b { color: red; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; } b { color: red; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a { color: pink; } b { color: red; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }b { color: red; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a { color: pink;
}b { color: red; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }b { color: red; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a { color: pink;
} b { color: red; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; } b { color: red; }
```
<!-- prettier-ignore -->
```css
a { color: pink;
}b { color: red; }
```
@@ -0,0 +1,88 @@
// @ts-nocheck
'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',
});
function rule(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.startsWith(';')) {
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;
@@ -0,0 +1,174 @@
# block-closing-brace-space-before
Require a single space or disallow whitespace before the closing brace of blocks.
<!-- prettier-ignore -->
```css
a { color: pink; }
/** ↑
* The space before this brace */
```
The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix all of the problems reported by this rule.
## 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 violations:
<!-- prettier-ignore -->
```css
a { color: pink;}
```
<!-- prettier-ignore -->
```css
a
{ color: pink;}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```css
a {
color: pink; }
```
### `"never"`
There _must never_ be whitespace before the closing brace.
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```css
a
{ color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a{ color: pink;}
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a { color: pink;}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink;}
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a {
color: pink;}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink;}
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a {
color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```css
a {
color: pink;}
```
@@ -0,0 +1,96 @@
// @ts-nocheck
'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',
});
function rule(expectation, options, context) {
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) => {
if (context.fix) {
if (expectation.startsWith('always')) {
statement.raws.after = statement.raws.after.replace(/\s*$/, ' ');
return;
}
if (expectation.startsWith('never')) {
statement.raws.after = statement.raws.after.replace(/\s*$/, '');
return;
}
}
report({
message: msg,
node: statement,
index,
result,
ruleName,
});
},
});
}
};
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
+75
View File
@@ -0,0 +1,75 @@
# block-no-empty
Disallow empty blocks.
<!-- prettier-ignore -->
```css
a { }
/** ↑
* Blocks like this */
```
## Options
### `true`
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a {}
```
<!-- prettier-ignore -->
```css
a { }
```
<!-- prettier-ignore -->
```css
@media print {
a {}
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a {
/* foo */
}
```
<!-- prettier-ignore -->
```css
@media print {
a {
color: pink;
}
}
```
## Optional secondary options
### `ignore: ["comments"]`
Exclude comments from being treated as content inside of a block.
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a {
/* foo */
}
```
<!-- prettier-ignore -->
```css
@media print {
a {
/* foo */
}
}
```
+83
View File
@@ -0,0 +1,83 @@
// @ts-nocheck
'use strict';
const _ = require('lodash');
const beforeBlockString = require('../../utils/beforeBlockString');
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 ruleName = 'block-no-empty';
const messages = ruleMessages(ruleName, {
rejected: 'Unexpected empty block',
});
function rule(primary, options = {}) {
return (root, result) => {
const validOptions = validateOptions(
result,
ruleName,
{
actual: primary,
possible: _.isBoolean,
},
{
actual: options,
possible: {
ignore: ['comments'],
},
optional: true,
},
);
if (!validOptions) {
return;
}
const ignoreComments = optionsMatches(options, 'ignore', 'comments');
// Check both kinds of statements: rules and at-rules
root.walkRules(check);
root.walkAtRules(check);
function check(statement) {
if (!hasEmptyBlock(statement) && !ignoreComments) {
return;
}
if (!hasBlock(statement)) {
return;
}
const hasCommentsOnly = statement.nodes.every((node) => node.type === 'comment');
if (!hasCommentsOnly) {
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;
@@ -0,0 +1,125 @@
# block-opening-brace-newline-after
Require a newline after the opening brace of blocks.
<!-- prettier-ignore -->
```css
a {
↑ color: pink; }
/** ↑
* The newline after this brace */
```
This rule allows an end-of-line comment followed by a newline. For example,
<!-- prettier-ignore -->
```css
a { /* end-of-line comment */
color: pink;
}
```
Refer to [combining rules](../../../docs/user-guide/rules/combine.md) 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.
The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix all of the problems reported by this rule.
## 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 violations:
<!-- prettier-ignore -->
```css
a{ color: pink; }
```
<!-- prettier-ignore -->
```css
a{ color: pink;
}
```
<!-- prettier-ignore -->
```css
a{ /* end-of-line comment
with a newline */
color: pink;
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a {
color: pink; }
```
<!-- prettier-ignore -->
```css
a
{
color: pink; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a{color: pink;
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a { color: pink;
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```css
a {color: pink;
}
```
@@ -0,0 +1,145 @@
// @ts-nocheck
'use strict';
const beforeBlockString = require('../../utils/beforeBlockString');
const blockString = require('../../utils/blockString');
const hasBlock = require('../../utils/hasBlock');
const hasEmptyBlock = require('../../utils/hasEmptyBlock');
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',
});
function rule(expectation, options, context) {
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;
}
const backupCommentNextBefores = new Map();
// next node with checking newlines after comment
function nextNode(startNode) {
if (!startNode || !startNode.next) return null;
if (startNode.type === 'comment') {
const reNewLine = /\r?\n/;
const newLineMatch = reNewLine.test(startNode.raws.before);
const next = startNode.next();
if (next && newLineMatch && !reNewLine.test(next.raws.before)) {
backupCommentNextBefores.set(next, next.raws.before);
next.raws.before = startNode.raws.before;
}
return nextNode(next);
}
return startNode;
}
// Allow an end-of-line comment
const nodeToCheck = nextNode(statement.first);
if (!nodeToCheck) {
return;
}
checker.afterOneOnly({
source: rawNodeString(nodeToCheck),
index: -1,
lineCheckStr: blockString(statement),
err: (m) => {
if (context.fix) {
if (expectation.startsWith('always')) {
const index = nodeToCheck.raws.before.search(/\r?\n/);
if (index >= 0) {
nodeToCheck.raws.before = nodeToCheck.raws.before.slice(index);
} else {
nodeToCheck.raws.before = context.newline + nodeToCheck.raws.before;
}
backupCommentNextBefores.delete(nodeToCheck);
return;
}
if (expectation === 'never-multi-line') {
// Restore the `before` of the node next to the comment node.
backupCommentNextBefores.forEach((before, node) => {
node.raws.before = before;
});
backupCommentNextBefores.clear();
// Fix
const reNewLine = /\r?\n/;
let fixTarget = statement.first;
while (fixTarget) {
if (reNewLine.test(fixTarget.raws.before)) {
fixTarget.raws.before = fixTarget.raws.before.replace(/\r?\n/g, '');
}
if (fixTarget.type !== 'comment') {
break;
}
fixTarget = fixTarget.next();
}
nodeToCheck.raws.before = '';
return;
}
}
report({
message: m,
node: statement,
index: beforeBlockString(statement, { noRawBefore: true }).length + 1,
result,
ruleName,
});
},
});
// Restore the `before` of the node next to the comment node.
backupCommentNextBefores.forEach((before, node) => {
node.raws.before = before;
});
}
};
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
@@ -0,0 +1,176 @@
# block-opening-brace-newline-before
Require a newline or disallow whitespace before the opening brace of blocks.
<!-- prettier-ignore -->
```css
a
{ color: pink; }
/** ↑
* The newline before this brace */
```
Refer to [combining rules](../../../docs/user-guide/rules/combine.md) 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.
The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix all of the problems reported by this rule.
## 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 violations:
<!-- prettier-ignore -->
```css
a{ color: pink; }
```
<!-- prettier-ignore -->
```css
a{ color: pink;
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a
{ color: pink; }
```
<!-- prettier-ignore -->
```css
a
{
color: pink; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a{ color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a
{ color: pink; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a{ color: pink; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a{
color: pink; }
```
<!-- prettier-ignore -->
```css
a {
color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a{ color: pink; }
```
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```css
a
{ color: pink; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a {
color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```css
a{
color: pink;}
```
@@ -0,0 +1,107 @@
// @ts-nocheck
'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',
});
function rule(expectation, options, context) {
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) => {
if (context.fix) {
if (expectation.startsWith('always')) {
const spaceIndex = statement.raws.between.search(/\s+$/);
if (spaceIndex >= 0) {
statement.raws.between =
statement.raws.between.slice(0, spaceIndex) +
context.newline +
statement.raws.between.slice(spaceIndex);
} else {
statement.raws.between += context.newline;
}
return;
}
if (expectation.startsWith('never')) {
statement.raws.between = statement.raws.between.replace(/\s*$/, '');
return;
}
}
report({
message: m,
node: statement,
index,
result,
ruleName,
});
},
});
}
};
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
@@ -0,0 +1,174 @@
# block-opening-brace-space-after
Require a single space or disallow whitespace after the opening brace of blocks.
<!-- prettier-ignore -->
```css
a { color: pink; }
/** ↑
* The space after this brace */
```
The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix all of the problems reported by this rule.
## 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 violations:
<!-- prettier-ignore -->
```css
a {color: pink; }
```
<!-- prettier-ignore -->
```css
a {
color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```css
a { color: pink;
}
```
### `"never"`
There _must never_ be whitespace after the opening brace.
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```css
a {
color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a {color: pink; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a {color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a {color: pink; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a {color: pink;
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a {color: pink; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a { color: pink;
}
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```css
a {color: pink;
}
```
@@ -0,0 +1,88 @@
// @ts-nocheck
'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',
});
function rule(expectation, options, context) {
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) => {
if (context.fix) {
if (expectation.startsWith('always')) {
statement.first.raws.before = ' ';
return;
}
if (expectation.startsWith('never')) {
statement.first.raws.before = '';
return;
}
}
report({
message: m,
node: statement,
index: beforeBlockString(statement, { noRawBefore: true }).length + 1,
result,
ruleName,
});
},
});
}
};
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
@@ -0,0 +1,218 @@
# block-opening-brace-space-before
Require a single space or disallow whitespace before the opening brace of blocks.
<!-- prettier-ignore -->
```css
a { color: pink; }
/** ↑
* The space before this brace */
```
The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix all of the problems reported by this rule.
## 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 violations:
<!-- prettier-ignore -->
```css
a{ color: pink; }
```
<!-- prettier-ignore -->
```css
a
{ color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```css
a {
color: pink; }
```
### `"never"`
There _must never_ be whitespace before the opening brace.
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```css
a
{ color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a{ color: pink; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a{ color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a{ color: pink; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a{
color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a{ color: pink; }
```
<!-- prettier-ignore -->
```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 violations:
<!-- prettier-ignore -->
```css
a {
color: pink; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: pink; }
```
<!-- prettier-ignore -->
```css
a{
color: pink;}
```
## Optional secondary options
### `ignoreAtRules: ["/regex/", /regex/, "non-regex"]`
Given:
```
["/fo/"]
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
@for ...
{}
```
<!-- prettier-ignore -->
```css
@for ...{}
```
### `ignoreSelectors: ["/regex/", /regex/, "non-regex"]`
Given:
```
[":root"]
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
:root
{}
```
<!-- prettier-ignore -->
```css
:root{}
```
@@ -0,0 +1,124 @@
// @ts-nocheck
'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',
});
function rule(expectation, options, context) {
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, _.isRegExp],
ignoreSelectors: [_.isString, _.isRegExp],
},
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;
}
// Return early if selector is to be ignored
if (optionsMatches(options, 'ignoreSelectors', statement.selector)) {
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) => {
if (context.fix) {
if (expectation.startsWith('always')) {
statement.raws.between = ' ';
return;
}
if (expectation.startsWith('never')) {
statement.raws.between = '';
return;
}
}
report({
message: m,
node: statement,
index,
result,
ruleName,
});
},
});
}
};
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
+116
View File
@@ -0,0 +1,116 @@
# color-function-notation
Specify modern or legacy notation for applicable color-functions.
<!-- prettier-ignore -->
```css
a { color: rgb(0 0 0 / 0.2) }
/** ↑
* This notation */
```
Modern color-functions use a comma-free syntax because functions in CSS are used to group/name a syntax chunk. They should work by the same rules that CSS grammar does in general: values are optional and re-orderable when possible, space-separated, and commas are used to separate repetitions only.
For legacy reasons, `rgb()` and `hsl()` also supports an alternate syntax that separates all of its arguments with commas. Also for legacy reasons, the `rgba()` and `hsla()` functions exist using the same comma-based syntax.
The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix some of the problems reported by this rule when the primary option is `"modern"`.
## Options
`string`: `"modern"|"legacy"`
### `"modern"`
Applicable color-functions _must always_ use modern notation.
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a { color: rgb(0, 0, 0) }
```
<!-- prettier-ignore -->
```css
a { color: rgba(12, 122, 231, 0.2) }
```
<!-- prettier-ignore -->
```css
a { color: hsla(270, 60%, 50%, 15%) }
```
<!-- prettier-ignore -->
```css
a { color: hsl(.75turn, 60%, 70%) }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: rgb(0 0 0) }
```
<!-- prettier-ignore -->
```css
a { color: rgb(12 122 231 / 0.2) }
```
<!-- prettier-ignore -->
```css
a { color: hsl(270 60% 50% / 15%) }
```
<!-- prettier-ignore -->
```css
a { color: hsl(.75turn 60% 70%) }
```
### `"legacy"`
Applicable color-functions _must always_ use the legacy notation.
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a { color: rgb(0 0 0) }
```
<!-- prettier-ignore -->
```css
a { color: rgb(12 122 231 / 0.2) }
```
<!-- prettier-ignore -->
```css
a { color: hsl(270 60% 50% / 15%) }
```
<!-- prettier-ignore -->
```css
a { color: hsl(.75turn 60% 70%) }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: rgb(0, 0, 0) }
```
<!-- prettier-ignore -->
```css
a { color: rgba(12, 122, 231, 0.2) }
```
<!-- prettier-ignore -->
```css
a { color: hsla(270, 60%, 50%, 15%) }
```
<!-- prettier-ignore -->
```css
a { color: hsl(.75turn, 60%, 70%) }
```
+108
View File
@@ -0,0 +1,108 @@
// @ts-nocheck
'use strict';
const valueParser = require('postcss-value-parser');
const declarationValueIndex = require('../../utils/declarationValueIndex');
const getDeclarationValue = require('../../utils/getDeclarationValue');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const setDeclarationValue = require('../../utils/setDeclarationValue');
const validateOptions = require('../../utils/validateOptions');
const ruleName = 'color-function-notation';
const messages = ruleMessages(ruleName, {
expected: (primary) => `Expected ${primary} color-function notation`,
});
const LEGACY_FUNCS = new Set(['rgba', 'hsla']);
const LEGACY_NOTATION_FUNCS = new Set(['rgb', 'rgba', 'hsl', 'hsla']);
function rule(primary, secondary, context) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: primary,
possible: ['modern', 'legacy'],
});
if (!validOptions) return;
root.walkDecls((decl) => {
let needsFix = false;
const parsedValue = valueParser(getDeclarationValue(decl));
parsedValue.walk((node) => {
const { value, type, sourceIndex, nodes } = node;
if (type !== 'function') return;
if (!LEGACY_NOTATION_FUNCS.has(value.toLowerCase())) return;
if (primary === 'modern' && !hasCommas(node)) return;
if (primary === 'legacy' && hasCommas(node)) return;
if (context.fix && primary === 'modern') {
let commaCount = 0;
// Convert punctuation
node.nodes = nodes.map((childNode) => {
if (isComma(childNode)) {
// Non-alpha commas to space and alpha commas to slashes
if (commaCount < 2) {
childNode.type = 'space';
childNode.value = atLeastOneSpace(childNode.after);
commaCount++;
} else {
childNode.value = '/';
childNode.before = atLeastOneSpace(childNode.before);
childNode.after = atLeastOneSpace(childNode.after);
}
}
return childNode;
});
// Remove trailing 'a' from legacy function name
if (LEGACY_FUNCS.has(node.value.toLowerCase())) {
node.value = node.value.slice(0, -1);
}
needsFix = true;
return;
}
report({
message: messages.expected(primary),
node: decl,
index: declarationValueIndex(decl) + sourceIndex,
result,
ruleName,
});
});
if (needsFix) {
setDeclarationValue(decl, parsedValue.toString());
}
});
};
}
function atLeastOneSpace(whitespace) {
return whitespace !== '' ? whitespace : ' ';
}
function isComma(node) {
return node.type === 'div' && node.value === ',';
}
function hasCommas(node) {
return node.nodes && node.nodes.some((childNode) => isComma(childNode));
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
+58
View File
@@ -0,0 +1,58 @@
# color-hex-case
Specify lowercase or uppercase for hex colors.
<!-- prettier-ignore -->
```css
a { color: #fff }
/** ↑
* This hex color */
```
The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix all of the problems reported by this rule.
## Options
`string`: `"lower"|"upper"`
### `"lower"`
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a { color: #FFF; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: #000; }
```
<!-- prettier-ignore -->
```css
a { color: #fff; }
```
### `"upper"`
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a { color: #fff; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: #000; }
```
<!-- prettier-ignore -->
```css
a { color: #FFF; }
```
+82
View File
@@ -0,0 +1,82 @@
// @ts-nocheck
'use strict';
const valueParser = require('postcss-value-parser');
const declarationValueIndex = require('../../utils/declarationValueIndex');
const getDeclarationValue = require('../../utils/getDeclarationValue');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const setDeclarationValue = require('../../utils/setDeclarationValue');
const validateOptions = require('../../utils/validateOptions');
const ruleName = 'color-hex-case';
const messages = ruleMessages(ruleName, {
expected: (actual, expected) => `Expected "${actual}" to be "${expected}"`,
});
const HEX = /^#[0-9A-Za-z]+/;
const IGNORED_FUNCTIONS = new Set(['url']);
function rule(expectation, options, context) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: ['lower', 'upper'],
});
if (!validOptions) {
return;
}
root.walkDecls((decl) => {
const parsedValue = valueParser(getDeclarationValue(decl));
let needsFix = false;
parsedValue.walk((node) => {
const { value } = node;
if (isIgnoredFunction(node)) return false;
if (!isHexColor(node)) return;
const expected = expectation === 'lower' ? value.toLowerCase() : value.toUpperCase();
if (value === expected) return;
if (context.fix) {
node.value = expected;
needsFix = true;
return;
}
report({
message: messages.expected(value, expected),
node: decl,
index: declarationValueIndex(decl) + node.sourceIndex,
result,
ruleName,
});
});
if (needsFix) {
setDeclarationValue(decl, parsedValue.toString());
}
});
};
}
function isIgnoredFunction({ type, value }) {
return type === 'function' && IGNORED_FUNCTIONS.has(value.toLowerCase());
}
function isHexColor({ type, value }) {
return type === 'word' && HEX.test(value);
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
+73
View File
@@ -0,0 +1,73 @@
# color-hex-length
Specify short or long notation for hex colors.
<!-- prettier-ignore -->
```css
a { color: #fff }
/** ↑
* This hex color */
```
The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix all of the problems reported by this rule.
## Options
`string`: `"short"|"long"`
### `"short"`
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a { color: #ffffff; }
```
<!-- prettier-ignore -->
```css
a { color: #ffffffaa; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: #fff; }
```
<!-- prettier-ignore -->
```css
a { color: #fffa; }
```
<!-- prettier-ignore -->
```css
a { color: #a4a4a4; }
```
### `"long"`
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a { color: #fff; }
```
<!-- prettier-ignore -->
```css
a { color: #fffa; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: #ffffff; }
```
<!-- prettier-ignore -->
```css
a { color: #ffffffaa; }
```
+120
View File
@@ -0,0 +1,120 @@
// @ts-nocheck
'use strict';
const valueParser = require('postcss-value-parser');
const declarationValueIndex = require('../../utils/declarationValueIndex');
const getDeclarationValue = require('../../utils/getDeclarationValue');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const setDeclarationValue = require('../../utils/setDeclarationValue');
const validateOptions = require('../../utils/validateOptions');
const ruleName = 'color-hex-length';
const messages = ruleMessages(ruleName, {
expected: (actual, expected) => `Expected "${actual}" to be "${expected}"`,
});
const HEX = /^#[0-9A-Za-z]+/;
const IGNORED_FUNCTIONS = new Set(['url']);
function rule(expectation, _, context) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: ['short', 'long'],
});
if (!validOptions) {
return;
}
root.walkDecls((decl) => {
const parsedValue = valueParser(getDeclarationValue(decl));
let needsFix = false;
parsedValue.walk((node) => {
const { value: hexValue } = node;
if (isIgnoredFunction(node)) return false;
if (!isHexColor(node)) return;
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;
const expectedHex = variant(hexValue);
if (context.fix) {
node.value = expectedHex;
needsFix = true;
return;
}
report({
message: messages.expected(hexValue, expectedHex),
node: decl,
index: declarationValueIndex(decl) + node.sourceIndex,
result,
ruleName,
});
});
if (needsFix) {
setDeclarationValue(decl, parsedValue.toString());
}
});
};
}
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 += 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;
}
function isIgnoredFunction({ type, value }) {
return type === 'function' && IGNORED_FUNCTIONS.has(value.toLowerCase());
}
function isHexColor({ type, value }) {
return type === 'word' && HEX.test(value);
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
+176
View File
@@ -0,0 +1,176 @@
# color-named
Require (where possible) or disallow named colors.
<!-- prettier-ignore -->
```css
a { color: black }
/** ↑
* This named color */
```
This rule ignores `$sass` and `@less` variable syntaxes.
## Options
`string`: `"always-where-possible"|"never"`
### `"always-where-possible"`
Colors _must always_, where possible, be named.
This will complain 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 violations:
<!-- prettier-ignore -->
```css
a { color: #000; }
```
<!-- prettier-ignore -->
```css
a { color: #f000; }
```
<!-- prettier-ignore -->
```css
a { color: #ff000000; }
```
<!-- prettier-ignore -->
```css
a { color: rgb(0, 0, 0); }
```
<!-- prettier-ignore -->
```css
a { color: rgb(0%, 0%, 0%); }
```
<!-- prettier-ignore -->
```css
a { color: rgba(0, 0, 0, 0); }
```
<!-- prettier-ignore -->
```css
a { color: hsl(0, 0%, 0%); }
```
<!-- prettier-ignore -->
```css
a { color: hwb(0, 0%, 100%); }
```
<!-- prettier-ignore -->
```css
a { color: gray(0); }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: black; }
```
<!-- prettier-ignore -->
```css
a { color: rgb(10, 0, 0); }
```
<!-- prettier-ignore -->
```css
a { color: rgb(0, 0, 0, 0.5); }
```
### `"never"`
Colors _must never_ be named.
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a { color: black; }
```
<!-- prettier-ignore -->
```css
a { color: white; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: #000; }
```
<!-- prettier-ignore -->
```css
a { color: rgb(0, 0, 0); }
```
<!-- prettier-ignore -->
```css
a { color: var(--white); }
```
## Optional secondary options
### `ignore: ["inside-function"]`
Ignore colors that are inside a function.
For example, with `"never"`.
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a {
color: map-get($colour, blue);
}
```
<!-- prettier-ignore -->
```css
a {
background-image: url(red);
}
```
### `ignoreProperties: ["/regex/", /regex/, "string"]`
For example with `"never"`.
Given:
```
["/^my-/", "composes"]
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a {
my-property: red;
}
```
<!-- prettier-ignore -->
```css
a {
my-other-property: red;
}
```
<!-- prettier-ignore -->
```css
a {
composes: red from './index.css';
}
```
+198
View File
@@ -0,0 +1,198 @@
// @ts-nocheck
'use strict';
// these algorithms are sourced from https://drafts.csswg.org/css-color/#color-conversion-code
function lin_sRGB(RGB) {
// convert an array of sRGB values in the range 0.0 - 1.0
// to linear light (un-companded) form.
// https://en.wikipedia.org/wiki/SRGB
return RGB.map((val) => {
if (val < 0.04045) {
return val / 12.92;
}
return ((val + 0.055) / 1.055) ** 2.4;
});
}
function matrixMultiple3d(matrix, vector) {
return [
matrix[0][0] * vector[0] + matrix[0][1] * vector[1] + matrix[0][2] * vector[2],
matrix[1][0] * vector[0] + matrix[1][1] * vector[1] + matrix[1][2] * vector[2],
matrix[2][0] * vector[0] + matrix[2][1] * vector[1] + matrix[2][2] * vector[2],
];
}
function srgb2xyz(srgb) {
return matrixMultiple3d(
[
[0.4124564, 0.3575761, 0.1804375],
[0.2126729, 0.7151522, 0.072175],
[0.0193339, 0.119192, 0.9503041],
],
srgb,
);
}
function chromaticAdaptationD65_D50(xyz) {
return matrixMultiple3d(
[
[1.0478112, 0.0228866, -0.050127],
[0.0295424, 0.9904844, -0.0170491],
[-0.0092345, 0.0150436, 0.7521316],
],
xyz,
);
}
function xyz2lab(xyzIn) {
// Assuming XYZ is relative to D50, convert to CIE Lab
// from CIE standard, which now defines these as a rational fraction
const ε = 216 / 24389; // 6^3/29^3
const κ = 24389 / 27; // 29^3/3^3
const white = [0.9642, 1.0, 0.8249]; // D50 reference white
// compute xyz, which is XYZ scaled relative to reference white
const xyz = xyzIn.map((value, i) => value / white[i]);
// now compute f
const f = xyz.map((value) => {
if (value > ε) {
return Math.cbrt(value);
}
return (κ * value + 16) / 116;
});
return [
116 * f[1] - 16, // L
500 * (f[0] - f[1]), // a
200 * (f[1] - f[2]), // b
];
}
function rgb2hsl(r, g, b) {
r /= 255;
g /= 255;
b /= 255;
let h;
let s;
let l;
const M = Math.max(r, g, b);
const m = Math.min(r, g, b);
const d = M - m;
if (d === 0) {
h = 0;
} else if (M === r) {
h = ((g - b) / d) % 6;
} else if (M === g) {
h = (b - r) / d + 2;
} else {
h = (r - g) / d + 4;
}
h *= 60;
if (h < 0) {
h += 360;
}
l = (M + m) / 2;
s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
s *= 100;
l *= 100;
return [Math.round(h), Math.round(s), Math.round(l)];
}
function rgb2hwb(rgb_r, rgb_g, rgb_b) {
rgb_r /= 255;
rgb_g /= 255;
rgb_b /= 255;
const w = Math.min(rgb_r, rgb_g, rgb_b);
const v = Math.max(rgb_r, rgb_g, rgb_b);
const b = 1 - v;
if (v === w) {
return [0, Math.round(w * 100), Math.round(b * 100)];
}
const f = rgb_r === w ? rgb_g - rgb_b : rgb_g === w ? rgb_b - rgb_r : rgb_r - rgb_g;
const i = rgb_r === w ? 3 : rgb_g === w ? 5 : 1;
return [
Math.round(((i - f / (v - w)) / 6) * 360) % 360,
Math.round(w * 100),
Math.round(b * 100),
];
}
function perc255(value) {
return `${Math.round((value * 100) / 255)}%`;
}
function generateColorFuncs(hexString) {
if (hexString.length !== 7) {
throw new Error(
`Invalid hex string color definition (${hexString}) - expected 6 character hex string`,
);
}
const rgb = [0, 0, 0];
for (let i = 0; i < 3; i += 1) {
rgb[i] = Number.parseInt(hexString.substr(2 * i + 1, 2), 16);
}
const hsl = rgb2hsl(rgb[0], rgb[1], rgb[2]);
const hwb = rgb2hwb(rgb[0], rgb[1], rgb[2]);
const func = [];
const rgbStr = `${rgb[0]},${rgb[1]},${rgb[2]}`;
const rgbPercStr = `${perc255(rgb[0])},${perc255(rgb[1])},${perc255(rgb[2])}`;
const hslStr = `${hsl[0]},${hsl[1]}%,${hsl[2]}%`;
const hwbStr = `${hwb[0]},${hwb[1]}%,${hwb[2]}%`;
// *very* convoluted process, just to be able to establish if the color
// is gray -- or not.
const linRgb = lin_sRGB([rgb[0] / 255, rgb[1] / 255, rgb[2] / 255]);
const xyz_d65 = srgb2xyz(linRgb);
const xyz_d50 = chromaticAdaptationD65_D50(xyz_d65);
const lab = xyz2lab(xyz_d50);
func.push(`rgb(${rgbStr})`);
func.push(`rgba(${rgbStr},1)`);
func.push(`rgba(${rgbStr},100%)`);
func.push(`rgb(${rgbPercStr})`);
func.push(`rgba(${rgbPercStr},1)`);
func.push(`rgba(${rgbPercStr},100%)`);
func.push(`hsl(${hslStr})`);
func.push(`hsla(${hslStr},1)`);
func.push(`hsla(${hslStr},100%)`);
func.push(`hwb(${hwbStr})`);
func.push(`hwb(${hwbStr},1)`);
func.push(`hwb(${hwbStr},100%)`);
// technically, this should be 0 - but then #808080 wouldn't even be gray
if (lab[1] * lab[1] < 0.01 && lab[2] * lab[2] < 0.01) {
// yay! gray!
const grayStr = Math.round(lab[0]);
func.push(`gray(${grayStr})`);
func.push(`gray(${grayStr},1)`);
func.push(`gray(${grayStr},100%)`);
func.push(`gray(${grayStr}%)`);
func.push(`gray(${grayStr}%,1)`);
func.push(`gray(${grayStr}%,100%)`);
}
return func;
}
module.exports = generateColorFuncs;
+169
View File
@@ -0,0 +1,169 @@
// @ts-nocheck
'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 namedColorDataHex = 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 generateColorFuncs = require('./generateColorFuncs');
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 insensitivity
const NODE_TYPES = new Set(['word', 'function']);
function rule(expectation, options) {
return (root, result) => {
const validOptions = validateOptions(
result,
ruleName,
{
actual: expectation,
possible: ['never', 'always-where-possible'],
},
{
actual: options,
possible: {
ignoreProperties: [_.isString, _.isRegExp],
ignore: ['inside-function'],
},
optional: true,
},
);
if (!validOptions) {
return;
}
const namedColors = Object.keys(namedColorDataHex);
const namedColorData = {};
namedColors.forEach((name) => {
const hex = namedColorDataHex[name];
namedColorData[name] = {
hex,
func: generateColorFuncs(hex[0]),
};
});
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;
const type = node.type;
const 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.has(type)) {
return;
}
// Check for named colors for "never" option
if (
expectation === 'never' &&
type === 'word' &&
namedColors.includes(value.toLowerCase())
) {
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.includes(normalizedFunctionString.toLowerCase())) {
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.includes(value.toLowerCase())) {
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;
+60
View File
@@ -0,0 +1,60 @@
# color-no-hex
Disallow hex colors.
<!-- prettier-ignore -->
```css
a { color: #333 }
/** ↑
* This hex color */
```
## Options
### `true`
The following patterns are considered violations:
<!-- prettier-ignore -->
```css
a { color: #000; }
```
<!-- prettier-ignore -->
```css
a { color: #fff1aa; }
```
<!-- prettier-ignore -->
```css
a { color: #123456aa; }
```
Hex values that are not valid also cause violations:
<!-- prettier-ignore -->
```css
a { color: #foobar; }
```
<!-- prettier-ignore -->
```css
a { color: #0000000000000000; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: black; }
```
<!-- prettier-ignore -->
```css
a { color: rgb(0, 0, 0); }
```
<!-- prettier-ignore -->
```css
a { color: rgba(0, 0, 0, 1); }
```
+60
View File
@@ -0,0 +1,60 @@
// @ts-nocheck
'use strict';
const valueParser = require('postcss-value-parser');
const declarationValueIndex = require('../../utils/declarationValueIndex');
const getDeclarationValue = require('../../utils/getDeclarationValue');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');
const ruleName = 'color-no-hex';
const messages = ruleMessages(ruleName, {
rejected: (hex) => `Unexpected hex color "${hex}"`,
});
const HEX = /^#[0-9A-Za-z]+/;
const IGNORED_FUNCTIONS = new Set(['url']);
function rule(actual) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual });
if (!validOptions) {
return;
}
root.walkDecls((decl) => {
const parsedValue = valueParser(getDeclarationValue(decl));
parsedValue.walk((node) => {
if (isIgnoredFunction(node)) return false;
if (!isHexColor(node)) return;
report({
message: messages.rejected(node.value),
node: decl,
index: declarationValueIndex(decl) + node.sourceIndex,
result,
ruleName,
});
});
});
};
}
function isIgnoredFunction({ type, value }) {
return type === 'function' && IGNORED_FUNCTIONS.has(value.toLowerCase());
}
function isHexColor({ type, value }) {
return type === 'word' && HEX.test(value);
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
+55
View File
@@ -0,0 +1,55 @@
# color-no-invalid-hex
Disallow invalid hex colors.
<!-- prettier-ignore -->
```css
a { color: #y3 }
/** ↑
* This hex color */
```
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 violations:
<!-- prettier-ignore -->
```css
a { color: #00; }
```
<!-- prettier-ignore -->
```css
a { color: #fff1az; }
```
<!-- prettier-ignore -->
```css
a { color: #12345aa; }
```
The following patterns are _not_ considered violations:
<!-- prettier-ignore -->
```css
a { color: #000; }
```
<!-- prettier-ignore -->
```css
a { color: #000f; }
```
<!-- prettier-ignore -->
```css
a { color: #fff1a0; }
```
<!-- prettier-ignore -->
```css
a { color: #123450aa; }
```
+54
View File
@@ -0,0 +1,54 @@
// @ts-nocheck
'use strict';
const declarationValueIndex = require('../../utils/declarationValueIndex');
const isValidHex = require('../../utils/isValidHex');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');
const valueParser = require('postcss-value-parser');
const ruleName = 'color-no-invalid-hex';
const messages = ruleMessages(ruleName, {
rejected: (hex) => `Unexpected invalid hex color "${hex}"`,
});
function rule(actual) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual });
if (!validOptions) {
return;
}
root.walkDecls((decl) => {
valueParser(decl.value).walk(({ value, type, sourceIndex }) => {
if (type === 'function' && value.endsWith('url')) return false;
if (type !== 'word') return;
const hexMatch = /^#[0-9A-Za-z]+/.exec(value);
if (!hexMatch) return;
const hexValue = hexMatch[0];
if (isValidHex(hexValue)) return;
report({
message: messages.rejected(hexValue),
node: decl,
index: declarationValueIndex(decl) + sourceIndex,
result,
ruleName,
});
});
});
};
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;

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