stylelint
Advanced tools
Comparing version 14.11.0 to 14.12.0
@@ -35,3 +35,3 @@ 'use strict'; | ||
* @property {boolean} [ignoreDisables] | ||
* @property {string} [ignorePath] | ||
* @property {string[]} [ignorePath] | ||
* @property {string[]} [ignorePattern] | ||
@@ -129,4 +129,5 @@ * @property {string} [noColor] | ||
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(). | ||
path can be absolute or relative to process.cwd(). You can repeat the | ||
option to provide multiple paths. By default, Stylelint looks for | ||
.stylelintignore in process.cwd(). | ||
@@ -278,2 +279,3 @@ --ignore-pattern, --ip | ||
type: 'string', | ||
isMultiple: true, | ||
}, | ||
@@ -280,0 +282,0 @@ ignorePattern: { |
@@ -31,2 +31,3 @@ 'use strict'; | ||
'charset', | ||
'container', | ||
'counter-style', | ||
@@ -33,0 +34,0 @@ 'custom-media', |
@@ -31,4 +31,9 @@ 'use strict'; | ||
const fontWeightAbsoluteKeywords = new Set(['bold']); | ||
const fontWeightAbsoluteKeywords = new Set(['normal', 'bold']); | ||
const fontWeightNonNumericKeywords = uniteSets( | ||
fontWeightRelativeKeywords, | ||
fontWeightAbsoluteKeywords, | ||
); | ||
const fontWeightNumericKeywords = new Set([ | ||
@@ -48,4 +53,3 @@ '100', | ||
basicKeywords, | ||
fontWeightRelativeKeywords, | ||
fontWeightAbsoluteKeywords, | ||
fontWeightNonNumericKeywords, | ||
fontWeightNumericKeywords, | ||
@@ -316,3 +320,5 @@ ); | ||
fontSizeKeywords, | ||
fontWeightAbsoluteKeywords, | ||
fontWeightKeywords, | ||
fontWeightNonNumericKeywords, | ||
fontWeightRelativeKeywords, | ||
@@ -319,0 +325,0 @@ gridAreaKeywords, |
@@ -42,2 +42,9 @@ 'use strict'; | ||
'fr', | ||
// Container query units | ||
'cqw', | ||
'cqh', | ||
'cqi', | ||
'cqb', | ||
'cqmin', | ||
'cqmax', | ||
]); | ||
@@ -44,0 +51,0 @@ |
@@ -16,3 +16,3 @@ 'use strict'; | ||
* | 'configFile' | ||
* >} [options] - The options to use when creating the Stylelint instance. | ||
* >} options - The options to use when creating the Stylelint instance. | ||
* @returns {Promise<import('stylelint').Config | undefined>} | ||
@@ -19,0 +19,0 @@ */ |
@@ -45,3 +45,4 @@ 'use strict'; | ||
report({ | ||
message: messages.rejected(node.value), | ||
message: messages.rejected, | ||
messageArgs: [node.value], | ||
node: decl, | ||
@@ -48,0 +49,0 @@ index, |
@@ -21,6 +21,7 @@ 'use strict'; | ||
url: 'https://stylelint.io/user-guide/rules/list/declaration-block-no-duplicate-properties', | ||
fixable: true, | ||
}; | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, secondaryOptions) => { | ||
const rule = (primary, secondaryOptions, context) => { | ||
return (root, result) => { | ||
@@ -62,9 +63,8 @@ const validOptions = validateOptions( | ||
eachDeclarationBlock(root, (eachDecl) => { | ||
/** @type {string[]} */ | ||
/** @type {import('postcss').Declaration[]} */ | ||
const decls = []; | ||
/** @type {string[]} */ | ||
const values = []; | ||
eachDecl((decl) => { | ||
const prop = decl.prop; | ||
const lowerProp = decl.prop.toLowerCase(); | ||
const value = decl.value; | ||
@@ -86,7 +86,7 @@ | ||
// Ignore the src property as commonly duplicated in at-fontface | ||
if (prop.toLowerCase() === 'src') { | ||
if (lowerProp === 'src') { | ||
return; | ||
} | ||
const indexDuplicate = decls.indexOf(prop.toLowerCase()); | ||
const indexDuplicate = decls.findIndex((d) => d.prop.toLowerCase() === lowerProp); | ||
@@ -97,2 +97,8 @@ if (indexDuplicate !== -1) { | ||
if (indexDuplicate !== decls.length - 1) { | ||
if (context.fix) { | ||
removePreviousDuplicate(decls, lowerProp); | ||
return; | ||
} | ||
report({ | ||
@@ -109,3 +115,4 @@ message: messages.rejected(prop), | ||
const duplicateValue = values[indexDuplicate] || ''; | ||
const duplicateDecl = decls[indexDuplicate]; | ||
const duplicateValue = duplicateDecl ? duplicateDecl.value : ''; | ||
@@ -115,2 +122,8 @@ if (ignorePrefixlessSameValues) { | ||
if (vendor.unprefixed(value) !== vendor.unprefixed(duplicateValue)) { | ||
if (context.fix) { | ||
removePreviousDuplicate(decls, lowerProp); | ||
return; | ||
} | ||
report({ | ||
@@ -130,2 +143,8 @@ message: messages.rejected(prop), | ||
if (value === duplicateValue) { | ||
if (context.fix) { | ||
removePreviousDuplicate(decls, lowerProp); | ||
return; | ||
} | ||
report({ | ||
@@ -149,2 +168,8 @@ message: messages.rejected(prop), | ||
if (context.fix) { | ||
removePreviousDuplicate(decls, lowerProp); | ||
return; | ||
} | ||
report({ | ||
@@ -159,4 +184,3 @@ message: messages.rejected(prop), | ||
decls.push(prop.toLowerCase()); | ||
values.push(value.toLowerCase()); | ||
decls.push(decl); | ||
}); | ||
@@ -167,2 +191,13 @@ }); | ||
/** | ||
* @param {import('postcss').Declaration[]} declarations | ||
* @param {string} lowerProperty | ||
* @returns {void} | ||
* */ | ||
function removePreviousDuplicate(declarations, lowerProperty) { | ||
const declToRemove = declarations.find((d) => d.prop.toLowerCase() === lowerProperty); | ||
if (declToRemove) declToRemove.remove(); | ||
} | ||
rule.ruleName = ruleName; | ||
@@ -169,0 +204,0 @@ rule.messages = messages; |
@@ -144,2 +144,6 @@ 'use strict'; | ||
root.walkDecls(/^font(-family)?$/i, (decl) => { | ||
if (!isStandardSyntaxValue(decl.value)) { | ||
return; | ||
} | ||
let fontFamilyNodes = makeMutableFontFamilies(findFontFamily(decl.value), decl); | ||
@@ -163,6 +167,2 @@ | ||
if (!isStandardSyntaxValue(rawFamily)) { | ||
return; | ||
} | ||
if (isVariable(rawFamily)) { | ||
@@ -169,0 +169,0 @@ return; |
@@ -6,11 +6,16 @@ 'use strict'; | ||
const declarationValueIndex = require('../../utils/declarationValueIndex'); | ||
const getDeclarationValue = require('../../utils/getDeclarationValue'); | ||
const isNumbery = require('../../utils/isNumbery'); | ||
const isStandardSyntaxValue = require('../../utils/isStandardSyntaxValue'); | ||
const isVariable = require('../../utils/isVariable'); | ||
const { fontWeightKeywords, fontWeightRelativeKeywords } = require('../../reference/keywords'); | ||
const { | ||
fontWeightKeywords, | ||
fontWeightNonNumericKeywords, | ||
fontWeightRelativeKeywords, | ||
} = require('../../reference/keywords'); | ||
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 { isAtRule } = require('../../utils/typeGuards'); | ||
@@ -26,11 +31,18 @@ const ruleName = 'font-weight-notation'; | ||
url: 'https://stylelint.io/user-guide/rules/list/font-weight-notation', | ||
fixable: true, | ||
}; | ||
const INHERIT_KEYWORD = 'inherit'; | ||
const INITIAL_KEYWORD = 'initial'; | ||
const NORMAL_KEYWORD = 'normal'; | ||
const WEIGHTS_WITH_KEYWORD_EQUIVALENTS = new Set(['400', '700']); | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, secondaryOptions) => { | ||
const KEYWORD_TO_NUMERIC = new Map([ | ||
['normal', '400'], | ||
['bold', '700'], | ||
]); | ||
const NUMERIC_TO_KEYWORD = new Map([ | ||
['400', 'normal'], | ||
['700', 'bold'], | ||
]); | ||
/** @type {import('stylelint').Rule<'numeric' | 'named-where-possible'>} */ | ||
const rule = (primary, secondaryOptions, context) => { | ||
return (root, result) => { | ||
@@ -57,45 +69,46 @@ const validOptions = validateOptions( | ||
const ignoreRelative = optionsMatches(secondaryOptions, 'ignore', 'relative'); | ||
root.walkDecls(/^font(-weight)?$/i, (decl) => { | ||
const prop = decl.prop.toLowerCase(); | ||
const isFontShorthandProp = decl.prop.toLowerCase() === 'font'; | ||
if (prop === 'font-weight') { | ||
checkWeight(decl, decl.value); | ||
} else if (prop === 'font') { | ||
checkFont(decl); | ||
} | ||
}); | ||
const parsedValue = valueParser(getDeclarationValue(decl)); | ||
const valueNodes = parsedValue.nodes; | ||
/** | ||
* @param {import('postcss').Declaration} decl | ||
*/ | ||
function checkFont(decl) { | ||
const valueNodes = findFontWeights(decl.value); | ||
const hasNumericFontWeight = valueNodes.some((node, index, nodes) => { | ||
return isNumbery(node.value) && !isDivNode(nodes[index - 1]); | ||
}); | ||
// We do not need to more carefully distinguish font-weight | ||
// numbers from unitless line-heights because line-heights in | ||
// `font` values need to be part of a font-size/line-height pair | ||
const hasNumericFontWeight = valueNodes.some(({ value }) => isNumbery(value)); | ||
for (const [index, valueNode] of valueNodes.entries()) { | ||
if (!isPossibleFontWeightNode(valueNode, index, valueNodes)) continue; | ||
for (const valueNode of valueNodes) { | ||
const value = valueNode.value; | ||
const lowerValue = value.toLowerCase(); | ||
const { value } = valueNode; | ||
if ( | ||
(lowerValue === NORMAL_KEYWORD && !hasNumericFontWeight) || | ||
isNumbery(value) || | ||
(lowerValue !== NORMAL_KEYWORD && fontWeightKeywords.has(lowerValue)) | ||
) { | ||
checkWeight(decl, value, valueNode); | ||
if (isFontShorthandProp) { | ||
if (value.toLowerCase() === NORMAL_KEYWORD && hasNumericFontWeight) { | ||
continue; // Not `normal` for font-weight | ||
} | ||
return; | ||
if (checkWeight(decl, valueNode)) { | ||
break; // Stop traverse if font-weight is processed | ||
} | ||
} | ||
checkWeight(decl, valueNode); | ||
} | ||
} | ||
if (context.fix) { | ||
// Autofix after the loop ends can prevent value nodes from changing their positions during the loop. | ||
setDeclarationValue(decl, parsedValue.toString()); | ||
} | ||
}); | ||
/** | ||
* @param {import('postcss').Declaration} decl | ||
* @param {string} weightValue | ||
* @param {import('postcss-value-parser').Node} [weightValueNode] | ||
* @param {import('postcss-value-parser').Node} weightValueNode | ||
* @returns {true | undefined} | ||
*/ | ||
function checkWeight(decl, weightValue, weightValueNode) { | ||
function checkWeight(decl, weightValueNode) { | ||
const weightValue = weightValueNode.value; | ||
if (!isStandardSyntaxValue(weightValue)) { | ||
@@ -109,35 +122,23 @@ return; | ||
if (includesOnlyFunction(weightValue)) { | ||
return; | ||
} | ||
const lowerWeightValue = weightValue.toLowerCase(); | ||
if (lowerWeightValue === INHERIT_KEYWORD || lowerWeightValue === INITIAL_KEYWORD) { | ||
if (ignoreRelative && fontWeightRelativeKeywords.has(lowerWeightValue)) { | ||
return; | ||
} | ||
if ( | ||
optionsMatches(secondaryOptions, 'ignore', 'relative') && | ||
fontWeightRelativeKeywords.has(lowerWeightValue) | ||
) { | ||
return; | ||
} | ||
if (primary === 'numeric') { | ||
const parent = decl.parent; | ||
if (!isNumbery(lowerWeightValue) && fontWeightNonNumericKeywords.has(lowerWeightValue)) { | ||
if (context.fix) { | ||
const numericValue = KEYWORD_TO_NUMERIC.get(lowerWeightValue); | ||
if (parent && isAtRule(parent) && parent.name.toLowerCase() === 'font-face') { | ||
// @font-face allows multiple values. | ||
for (const valueNode of findFontWeights(weightValue)) { | ||
if (!isNumbery(valueNode.value)) { | ||
return complain(messages.expected('numeric'), valueNode.value, valueNode); | ||
if (numericValue) { | ||
weightValueNode.value = numericValue; | ||
return true; | ||
} | ||
} | ||
return; | ||
} | ||
complain(messages.expected('numeric'), weightValueNode); | ||
if (!isNumbery(weightValue)) { | ||
return complain(messages.expected('numeric'), weightValue, weightValueNode); | ||
return true; | ||
} | ||
@@ -147,12 +148,26 @@ } | ||
if (primary === 'named-where-possible') { | ||
if (isNumbery(weightValue)) { | ||
if (WEIGHTS_WITH_KEYWORD_EQUIVALENTS.has(weightValue)) { | ||
complain(messages.expected('named'), weightValue, weightValueNode); | ||
if (isNumbery(lowerWeightValue) && NUMERIC_TO_KEYWORD.has(lowerWeightValue)) { | ||
if (context.fix) { | ||
const keyword = NUMERIC_TO_KEYWORD.get(lowerWeightValue); | ||
if (keyword) { | ||
weightValueNode.value = keyword; | ||
} | ||
return true; | ||
} | ||
return; | ||
complain(messages.expected('named'), weightValueNode); | ||
return true; | ||
} | ||
if (!fontWeightKeywords.has(lowerWeightValue) && lowerWeightValue !== NORMAL_KEYWORD) { | ||
return complain(messages.invalidNamed(weightValue), weightValue, weightValueNode); | ||
if ( | ||
decl.prop.toLowerCase() === 'font-weight' && | ||
!fontWeightKeywords.has(lowerWeightValue) && | ||
lowerWeightValue !== NORMAL_KEYWORD | ||
) { | ||
complain(messages.invalidNamed(weightValue), weightValueNode); | ||
return true; | ||
} | ||
@@ -163,8 +178,7 @@ } | ||
* @param {string} message | ||
* @param {string} value | ||
* @param {import('postcss-value-parser').Node | undefined} valueNode | ||
* @param {import('postcss-value-parser').Node} valueNode | ||
*/ | ||
function complain(message, value, valueNode) { | ||
const index = declarationValueIndex(decl) + (valueNode ? valueNode.sourceIndex : 0); | ||
const endIndex = index + value.length; | ||
function complain(message, valueNode) { | ||
const index = declarationValueIndex(decl) + valueNode.sourceIndex; | ||
const endIndex = index + valueNode.value.length; | ||
@@ -185,29 +199,24 @@ report({ | ||
/** | ||
* @param {string} value | ||
* @returns {import('postcss-value-parser').Node[]} | ||
* @param {import('postcss-value-parser').Node | undefined} node | ||
* @returns {boolean} | ||
*/ | ||
function findFontWeights(value) { | ||
return valueParser(value).nodes.filter((node, index, nodes) => { | ||
if (node.type !== 'word') return false; | ||
// Exclude `<font-size>/<line-height>` format like `16px/3`. | ||
const prevNode = nodes[index - 1]; | ||
const nextNode = nodes[index + 1]; | ||
if (prevNode && prevNode.type === 'div') return false; | ||
if (nextNode && nextNode.type === 'div') return false; | ||
return true; | ||
}); | ||
function isDivNode(node) { | ||
return node !== undefined && node.type === 'div'; | ||
} | ||
/** | ||
* @param {string} value | ||
* @param {import('postcss-value-parser').Node} node | ||
* @param {number} index | ||
* @param {import('postcss-value-parser').Node[]} nodes | ||
* @returns {boolean} | ||
*/ | ||
function includesOnlyFunction(value) { | ||
return valueParser(value).nodes.every(({ type }) => { | ||
return type === 'function' || type === 'comment' || type === 'space'; | ||
}); | ||
function isPossibleFontWeightNode(node, index, nodes) { | ||
if (node.type !== 'word') return false; | ||
// Exclude `<font-size>/<line-height>` format like `16px/3`. | ||
if (isDivNode(nodes[index - 1])) return false; | ||
if (isDivNode(nodes[index + 1])) return false; | ||
return true; | ||
} | ||
@@ -214,0 +223,0 @@ |
@@ -37,13 +37,6 @@ 'use strict'; | ||
* @param {string} string | ||
* @param {{ ignoreEmptyLines?: boolean, isRootFirst?: boolean }} [options] | ||
* @param {{ ignoreEmptyLines: boolean, isRootFirst: boolean }} options | ||
* @returns {number} | ||
*/ | ||
function findErrorStartIndex( | ||
lastEOLIndex, | ||
string, | ||
{ ignoreEmptyLines, isRootFirst } = { | ||
ignoreEmptyLines: false, | ||
isRootFirst: false, | ||
}, | ||
) { | ||
function findErrorStartIndex(lastEOLIndex, string, { ignoreEmptyLines, isRootFirst }) { | ||
const eolWhitespaceIndex = lastEOLIndex - 1; | ||
@@ -50,0 +43,0 @@ |
@@ -6,5 +6,6 @@ 'use strict'; | ||
const report = require('../../utils/report'); | ||
const optionsMatches = require('../../utils/optionsMatches'); | ||
const ruleMessages = require('../../utils/ruleMessages'); | ||
const validateOptions = require('../../utils/validateOptions'); | ||
const { isRegExp, isString } = require('../../utils/validateTypes'); | ||
const { isRegExp, isString, isBoolean } = require('../../utils/validateTypes'); | ||
@@ -21,9 +22,21 @@ const ruleName = 'selector-disallowed-list'; | ||
/** @type {import('stylelint').Rule<string | RegExp | Array<string | RegExp>>} */ | ||
const rule = (primary) => { | ||
/** @type {import('stylelint').Rule<string | RegExp | Array<string | RegExp>, { splitList: boolean, ignore: string[] }>} */ | ||
const rule = (primary, secondaryOptions) => { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [isString, isRegExp], | ||
}); | ||
const validOptions = validateOptions( | ||
result, | ||
ruleName, | ||
{ | ||
actual: primary, | ||
possible: [isString, isRegExp], | ||
}, | ||
{ | ||
actual: secondaryOptions, | ||
possible: { | ||
ignore: ['inside-block'], | ||
splitList: [isBoolean], | ||
}, | ||
optional: true, | ||
}, | ||
); | ||
@@ -34,2 +47,5 @@ if (!validOptions) { | ||
const ignoreInsideBlock = optionsMatches(secondaryOptions, 'ignore', 'inside-block'); | ||
const splitList = secondaryOptions && secondaryOptions.splitList; | ||
root.walkRules((ruleNode) => { | ||
@@ -40,17 +56,38 @@ if (!isStandardSyntaxRule(ruleNode)) { | ||
const { selector, raws } = ruleNode; | ||
if (ignoreInsideBlock) { | ||
const { parent } = ruleNode; | ||
const isInsideBlock = parent && parent.type !== 'root'; | ||
if (!matchesStringOrRegExp(selector, primary)) { | ||
return; | ||
if (isInsideBlock) { | ||
return; | ||
} | ||
} | ||
const word = (raws.selector && raws.selector.raw) || selector; | ||
if (splitList) { | ||
ruleNode.selectors.forEach((selector) => { | ||
if (matchesStringOrRegExp(selector, primary)) { | ||
report({ | ||
result, | ||
ruleName, | ||
message: messages.rejected(selector), | ||
node: ruleNode, | ||
word: selector, | ||
}); | ||
} | ||
}); | ||
} else { | ||
const { selector, raws } = ruleNode; | ||
report({ | ||
result, | ||
ruleName, | ||
message: messages.rejected(selector), | ||
node: ruleNode, | ||
word, | ||
}); | ||
if (matchesStringOrRegExp(selector, primary)) { | ||
const word = (raws.selector && raws.selector.raw) || selector; | ||
report({ | ||
result, | ||
ruleName, | ||
message: messages.rejected(selector), | ||
node: ruleNode, | ||
word, | ||
}); | ||
} | ||
} | ||
}); | ||
@@ -57,0 +94,0 @@ }; |
@@ -10,3 +10,3 @@ 'use strict'; | ||
const validateOptions = require('../../utils/validateOptions'); | ||
const { isString } = require('../../utils/validateTypes'); | ||
const { isString, isRegExp } = require('../../utils/validateTypes'); | ||
@@ -34,3 +34,3 @@ const ruleName = 'selector-no-vendor-prefix'; | ||
possible: { | ||
ignoreSelectors: [isString], | ||
ignoreSelectors: [isString, isRegExp], | ||
}, | ||
@@ -37,0 +37,0 @@ optional: true, |
@@ -21,3 +21,3 @@ 'use strict'; | ||
const vendor = require('../../utils/vendor'); | ||
const { isString } = require('../../utils/validateTypes'); | ||
const { isString, isRegExp } = require('../../utils/validateTypes'); | ||
const { isAtRule } = require('../../utils/typeGuards'); | ||
@@ -45,3 +45,3 @@ | ||
possible: { | ||
ignorePseudoClasses: [isString], | ||
ignorePseudoClasses: [isString, isRegExp], | ||
}, | ||
@@ -48,0 +48,0 @@ optional: true, |
@@ -12,3 +12,3 @@ 'use strict'; | ||
const vendor = require('../../utils/vendor'); | ||
const { isString } = require('../../utils/validateTypes'); | ||
const { isString, isRegExp } = require('../../utils/validateTypes'); | ||
@@ -35,3 +35,3 @@ const ruleName = 'selector-pseudo-element-no-unknown'; | ||
possible: { | ||
ignorePseudoElements: [isString], | ||
ignorePseudoElements: [isString, isRegExp], | ||
}, | ||
@@ -38,0 +38,0 @@ optional: true, |
@@ -11,3 +11,3 @@ 'use strict'; | ||
const validateOptions = require('../../utils/validateOptions'); | ||
const { isString } = require('../../utils/validateTypes'); | ||
const { isString, isRegExp } = require('../../utils/validateTypes'); | ||
const { mixedCaseSvgTypeSelectors } = require('../../reference/selectors'); | ||
@@ -39,3 +39,3 @@ | ||
possible: { | ||
ignoreTypes: [isString], | ||
ignoreTypes: [isString, isRegExp], | ||
}, | ||
@@ -42,0 +42,0 @@ optional: true, |
@@ -6,3 +6,2 @@ 'use strict'; | ||
const optionsMatches = require('../../utils/optionsMatches'); | ||
const postcss = require('postcss'); | ||
const report = require('../../utils/report'); | ||
@@ -14,2 +13,4 @@ const ruleMessages = require('../../utils/ruleMessages'); | ||
const { isNumber } = require('../../utils/validateTypes'); | ||
const getDeclarationValue = require('../../utils/getDeclarationValue'); | ||
const getDimension = require('../../utils/getDimension'); | ||
@@ -57,53 +58,41 @@ const ruleName = 'time-min-milliseconds'; | ||
const propertyValue = decl.value; | ||
const parsedValue = valueParser(getDeclarationValue(decl)); | ||
let timeValueCount = 0; | ||
if ( | ||
longhandTimeProperties.has(propertyName) && | ||
!isIgnoredProperty(propertyName) && | ||
!isAcceptableTime(propertyValue) | ||
) { | ||
complain(decl, 0, propertyValue.length); | ||
} | ||
parsedValue.walk((node) => { | ||
const { value, sourceIndex } = node; | ||
const dimension = getDimension(node); | ||
if (shorthandTimeProperties.has(propertyName)) { | ||
const valueListList = postcss.list.comma(propertyValue); | ||
if ( | ||
longhandTimeProperties.has(propertyName) && | ||
!isIgnoredProperty(propertyName) && | ||
!isAcceptableTime(dimension) | ||
) { | ||
complain(decl, 0, propertyValue.length); | ||
} | ||
for (const valueListString of valueListList) { | ||
const valueList = postcss.list.space(valueListString); | ||
if (!shorthandTimeProperties.has(propertyName)) return; | ||
if (ignoreDelay) { | ||
// Check only duration time values | ||
const duration = getDuration(valueList); | ||
timeValueCount = calcTimeValueCount(dimension, value, timeValueCount); | ||
if (duration && !isAcceptableTime(duration)) { | ||
complain(decl, propertyValue.indexOf(duration), duration.length); | ||
} | ||
} else { | ||
// Check all time values | ||
for (const value of valueList) { | ||
if (!isAcceptableTime(value)) { | ||
complain(decl, propertyValue.indexOf(value), value.length); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
if (isAcceptableTime(dimension) || (ignoreDelay && timeValueCount !== 1)) return; | ||
complain(decl, sourceIndex, value.length); | ||
}); | ||
}); | ||
/** | ||
* Get the duration within an `animation` or `transition` shorthand property value. | ||
* | ||
* @param {string[]} valueList | ||
* @returns {string | undefined} | ||
* @param {{unit: string | null, number: string | null}} dimension | ||
* @param {string} value | ||
* @param {number} valueTimeCount | ||
* @returns {number} | ||
*/ | ||
function getDuration(valueList) { | ||
for (const value of valueList) { | ||
const parsedTime = valueParser.unit(value); | ||
function calcTimeValueCount(dimension, value, valueTimeCount) { | ||
const { unit } = dimension; | ||
if (!parsedTime) continue; | ||
if (unit !== null) valueTimeCount++; | ||
// The first numeric value in an animation shorthand is the duration. | ||
return value; | ||
} | ||
if (value === ',') valueTimeCount = 0; | ||
return undefined; | ||
return valueTimeCount; | ||
} | ||
@@ -124,11 +113,11 @@ | ||
/** | ||
* @param {string} time | ||
* @param {import('postcss-value-parser').Dimension | {unit: null, number: null}} dimension | ||
* @returns {boolean} | ||
*/ | ||
function isAcceptableTime(time) { | ||
const parsedTime = valueParser.unit(time); | ||
function isAcceptableTime(dimension) { | ||
const { unit, number } = dimension; | ||
if (!parsedTime) return true; | ||
if (unit === null || number === null) return true; | ||
const numTime = Number(parsedTime.number); | ||
const numTime = Number(number); | ||
@@ -139,9 +128,9 @@ if (numTime <= 0) { | ||
const unit = parsedTime.unit.toLowerCase(); | ||
const timeUnit = unit.toLowerCase(); | ||
if (unit === 'ms' && numTime < minimum) { | ||
if (timeUnit === 'ms' && numTime < minimum) { | ||
return false; | ||
} | ||
if (unit === 's' && numTime * 1000 < minimum) { | ||
if (timeUnit === 's' && numTime * 1000 < minimum) { | ||
return false; | ||
@@ -148,0 +137,0 @@ } |
@@ -13,23 +13,32 @@ 'use strict'; | ||
/** | ||
* @param {{ cwd: string, ignorePath?: string, ignorePattern?: string[] }} options | ||
* @param {{ cwd: string, ignorePath?: string | string[], ignorePattern?: string[] }} options | ||
* @return {import('ignore').Ignore} | ||
*/ | ||
module.exports = function getFileIgnorer(options) { | ||
const ignoreFilePath = options.ignorePath || DEFAULT_IGNORE_FILENAME; | ||
const absoluteIgnoreFilePath = path.isAbsolute(ignoreFilePath) | ||
? ignoreFilePath | ||
: path.resolve(options.cwd, ignoreFilePath); | ||
let ignoreText = ''; | ||
const ignorer = ignore(); | ||
const ignorePaths = [options.ignorePath || []].flat(); | ||
try { | ||
ignoreText = fs.readFileSync(absoluteIgnoreFilePath, 'utf8'); | ||
} catch (readError) { | ||
if (!isPathNotFoundError(readError)) { | ||
throw readError; | ||
if (ignorePaths.length === 0) { | ||
ignorePaths.push(DEFAULT_IGNORE_FILENAME); | ||
} | ||
for (const ignoreFilePath of ignorePaths) { | ||
const absoluteIgnoreFilePath = path.isAbsolute(ignoreFilePath) | ||
? ignoreFilePath | ||
: path.resolve(options.cwd, ignoreFilePath); | ||
try { | ||
const ignoreText = fs.readFileSync(absoluteIgnoreFilePath, 'utf8'); | ||
ignorer.add(ignoreText); | ||
} catch (readError) { | ||
if (!isPathNotFoundError(readError)) { | ||
throw readError; | ||
} | ||
} | ||
} | ||
return ignore() | ||
.add(ignoreText) | ||
.add(options.ignorePattern || []); | ||
ignorer.add(options.ignorePattern || []); | ||
return ignorer; | ||
}; |
@@ -9,6 +9,4 @@ 'use strict'; | ||
*/ | ||
module.exports = function (value) { | ||
/* eslint-disable eqeqeq */ | ||
return value.toString().trim().length !== 0 && Number(value) == value; | ||
/* eslint-enable eqeqeq */ | ||
module.exports = function isNumbery(value) { | ||
return value.toString().trim().length !== 0 && Number(value) == value; // eslint-disable-line eqeqeq | ||
}; |
'use strict'; | ||
const util = require('util'); | ||
/** | ||
@@ -18,3 +20,3 @@ * Report a problem. | ||
module.exports = function report(problem) { | ||
const { ruleName, result, message, line, node, index, endIndex, word } = problem; | ||
const { ruleName, result, message, messageArgs, line, node, index, endIndex, word } = problem; | ||
@@ -108,6 +110,24 @@ result.stylelint = result.stylelint || { | ||
const warningMessage = | ||
(result.stylelint.customMessages && result.stylelint.customMessages[ruleName]) || message; | ||
const { customMessages } = result.stylelint; | ||
const warningMessage = buildWarningMessage( | ||
(customMessages && customMessages[ruleName]) || message, | ||
messageArgs, | ||
); | ||
result.warn(warningMessage, warningProperties); | ||
}; | ||
/** | ||
* @param {import('stylelint').RuleMessage} message | ||
* @param {import('stylelint').Problem['messageArgs']} messageArgs | ||
* @returns {string} | ||
*/ | ||
function buildWarningMessage(message, messageArgs) { | ||
const args = messageArgs || []; | ||
if (typeof message === 'string') { | ||
return util.format(message, ...args); | ||
} | ||
return message(...args); | ||
} |
{ | ||
"name": "stylelint", | ||
"version": "14.11.0", | ||
"version": "14.12.0", | ||
"description": "A mighty, modern CSS linter.", | ||
@@ -46,6 +46,8 @@ "keywords": [ | ||
"lint:types": "tsc", | ||
"prepare": "husky install", | ||
"prepare": "husky install && patch-package", | ||
"release": "np", | ||
"pretest": "npm run lint", | ||
"test": "jest --coverage", | ||
"version": "changeset version", | ||
"postversion": "git restore package.json", | ||
"watch": "jest --watch" | ||
@@ -118,3 +120,3 @@ }, | ||
"debug": "^4.3.4", | ||
"fast-glob": "^3.2.11", | ||
"fast-glob": "^3.2.12", | ||
"fastest-levenshtein": "^1.0.16", | ||
@@ -146,3 +148,3 @@ "file-entry-cache": "^6.0.1", | ||
"style-search": "^0.1.0", | ||
"supports-hyperlinks": "^2.2.0", | ||
"supports-hyperlinks": "^2.3.0", | ||
"svg-tags": "^1.0.0", | ||
@@ -154,6 +156,6 @@ "table": "^6.8.0", | ||
"devDependencies": { | ||
"@changesets/cli": "^2.24.3", | ||
"@changesets/cli": "^2.24.4", | ||
"@changesets/get-github-info": "^0.5.1", | ||
"@stylelint/prettier-config": "^2.0.0", | ||
"@stylelint/remark-preset": "^3.0.0", | ||
"@stylelint/remark-preset": "^4.0.0", | ||
"@types/balanced-match": "^1.0.2", | ||
@@ -175,9 +177,8 @@ "@types/debug": "^4.1.7", | ||
"deepmerge": "^4.2.2", | ||
"eslint": "^8.22.0", | ||
"eslint-config-stylelint": "^15.1.0", | ||
"eslint-plugin-jest": "^26.8.5", | ||
"eslint": "^8.23.1", | ||
"eslint-config-stylelint": "^16.0.0", | ||
"husky": "^8.0.1", | ||
"jest": "^28.1.3", | ||
"jest-preset-stylelint": "^5.0.4", | ||
"jest-watch-typeahead": "^2.0.0", | ||
"jest-watch-typeahead": "^2.2.0", | ||
"lint-staged": "^13.0.3", | ||
@@ -187,2 +188,3 @@ "node-fetch": "^3.2.10", | ||
"npm-run-all": "^4.1.5", | ||
"patch-package": "^6.4.7", | ||
"postcss-html": "^1.5.0", | ||
@@ -193,6 +195,5 @@ "postcss-import": "^14.1.0", | ||
"postcss-scss": "^4.0.4", | ||
"prettier": "2.7.1", | ||
"remark-cli": "^11.0.0", | ||
"sugarss": "^4.0.1", | ||
"typescript": "^4.7.4" | ||
"typescript": "^4.8.3" | ||
}, | ||
@@ -199,0 +200,0 @@ "engines": { |
@@ -92,3 +92,3 @@ declare module 'stylelint' { | ||
ruleSeverities: { [ruleName: string]: Severity }; | ||
customMessages: { [ruleName: string]: any }; | ||
customMessages: { [ruleName: string]: RuleMessage }; | ||
ruleMetadata: { [ruleName: string]: Partial<RuleMeta> }; | ||
@@ -158,4 +158,6 @@ quiet?: boolean; | ||
export type RuleMessages = { [message: string]: string | RuleMessageFunc }; | ||
export type RuleMessage = string | RuleMessageFunc; | ||
export type RuleMessages = { [message: string]: RuleMessage }; | ||
export type RuleOptionsPossibleFunc = (value: unknown) => boolean; | ||
@@ -226,3 +228,3 @@ | ||
ignoreDisables?: boolean; | ||
ignorePath?: string; | ||
ignorePath?: string | string[]; | ||
ignorePattern?: string[]; | ||
@@ -356,3 +358,4 @@ reportDescriptionlessDisables?: boolean; | ||
result: PostcssResult; | ||
message: string; | ||
message: RuleMessage; | ||
messageArgs?: Parameters<RuleMessage> | undefined; | ||
node: PostCSS.Node; | ||
@@ -359,0 +362,0 @@ /** |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
882313
39
27797
Updatedfast-glob@^3.2.12
Updatedsupports-hyperlinks@^2.3.0