stylelint
Advanced tools
Comparing version 15.5.0 to 15.6.0
@@ -427,2 +427,6 @@ 'use strict'; | ||
if (stylelint._options.fix) { | ||
augmentedConfig.fix = stylelint._options.fix; | ||
} | ||
return augmentedConfig; | ||
@@ -429,0 +433,0 @@ } |
@@ -115,3 +115,3 @@ 'use strict'; | ||
!disableFix && | ||
stylelintOptions.fix && | ||
config.fix && | ||
// Next two conditionals are temporary measures until #2643 is resolved | ||
@@ -118,0 +118,0 @@ isFileFixCompatible && |
@@ -13,2 +13,3 @@ 'use strict'; | ||
const validateOptions = require('../../utils/validateOptions'); | ||
const optionsMatches = require('../../utils/optionsMatches'); | ||
@@ -30,11 +31,24 @@ const ruleName = 'color-function-notation'; | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
const rule = (primary, secondaryOptions, context) => { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: ['modern', 'legacy'], | ||
}); | ||
const validOptions = validateOptions( | ||
result, | ||
ruleName, | ||
{ | ||
actual: primary, | ||
possible: ['modern', 'legacy'], | ||
}, | ||
{ | ||
actual: secondaryOptions, | ||
possible: { | ||
ignore: ['with-var-inside'], | ||
}, | ||
optional: true, | ||
}, | ||
); | ||
if (!validOptions) return; | ||
const ignoreWithVarInside = optionsMatches(secondaryOptions, 'ignore', 'with-var-inside'); | ||
root.walkDecls((decl) => { | ||
@@ -51,2 +65,6 @@ let needsFix = false; | ||
if (ignoreWithVarInside && containsVariable(nodes)) { | ||
return; | ||
} | ||
if (!LEGACY_NOTATION_FUNCS.has(value.toLowerCase())) return; | ||
@@ -119,2 +137,9 @@ | ||
/** | ||
* @param {import('postcss-value-parser').Node[]} nodes | ||
*/ | ||
function containsVariable(nodes) { | ||
return nodes.some(({ type, value }) => type === 'function' && value.toLowerCase() === 'var'); | ||
} | ||
/** | ||
* @param {import('postcss-value-parser').Node} node | ||
@@ -121,0 +146,0 @@ * @returns {node is import('postcss-value-parser').DivNode} |
@@ -119,1 +119,35 @@ # color-function-notation | ||
``` | ||
## Optional secondary options | ||
### `ignore: ["with-var-inside"]` | ||
Ignore color functions containing variables. | ||
Given: | ||
```json | ||
["modern", { "ignore": ["with-var-inside"] }] | ||
``` | ||
The following patterns are _not_ considered problems: | ||
```css | ||
a { | ||
color: rgba(var(--foo), 0.5); | ||
} | ||
``` | ||
Given: | ||
```json | ||
["legacy", { "ignore": ["with-var-inside"] }] | ||
``` | ||
The following patterns are _not_ considered problems: | ||
```css | ||
a { | ||
color: rgba(var(--foo) / 0.5); | ||
} | ||
``` |
@@ -7,2 +7,3 @@ 'use strict'; | ||
const isStandardSyntaxProperty = require('../../utils/isStandardSyntaxProperty'); | ||
const isStandardSyntaxValue = require('../../utils/isStandardSyntaxValue'); | ||
const optionsMatches = require('../../utils/optionsMatches'); | ||
@@ -33,2 +34,3 @@ const report = require('../../utils/report'); | ||
const isEqualValueNodes = (nodes1, nodes2) => { | ||
// Different lengths indicate different syntaxes. | ||
if (nodes1.length !== nodes2.length) { | ||
@@ -42,2 +44,3 @@ return false; | ||
// Different types indicate different syntaxes. | ||
if (typeof node1 === 'undefined' || typeof node2 === 'undefined' || node1.type !== node2.type) { | ||
@@ -47,2 +50,28 @@ return false; | ||
const node1Name = 'name' in node1 ? String(node1.name) : ''; | ||
const node2Name = 'name' in node2 ? String(node2.name) : ''; | ||
// Custom properties have unknown value syntaxes but are equal for CSS parsers. | ||
if ( | ||
node1.type === 'Identifier' && | ||
isCustomProperty(node1Name) && | ||
node2.type === 'Identifier' && | ||
isCustomProperty(node2Name) | ||
) { | ||
continue; | ||
} | ||
// Different ident or function names indicate different syntaxes. | ||
if (node1Name.toLowerCase() !== node2Name.toLowerCase()) { | ||
return false; | ||
} | ||
const node1Unit = 'unit' in node1 ? node1.unit : ''; | ||
const node2Unit = 'unit' in node2 ? node2.unit : ''; | ||
// Different units indicate different syntaxes. | ||
if (node1Unit !== node2Unit) { | ||
return false; | ||
} | ||
const node1Children = hasChildren(node1) ? node1.children.toArray() : null; | ||
@@ -52,9 +81,2 @@ const node2Children = hasChildren(node2) ? node2.children.toArray() : null; | ||
if (Array.isArray(node1Children) && Array.isArray(node2Children)) { | ||
const node1Name = 'name' in node1 ? String(node1.name) : ''; | ||
const node2Name = 'name' in node2 ? String(node2.name) : ''; | ||
if (node1Name.toLowerCase() !== node2Name.toLowerCase()) { | ||
return false; | ||
} | ||
if (isEqualValueNodes(node1Children, node2Children)) { | ||
@@ -66,9 +88,2 @@ continue; | ||
} | ||
const node1Unit = 'unit' in node1 ? node1.unit : ''; | ||
const node2Unit = 'unit' in node2 ? node2.unit : ''; | ||
if (node1Unit !== node2Unit) { | ||
return false; | ||
} | ||
} | ||
@@ -85,4 +100,16 @@ | ||
const value1Node = parse(value1, { context: 'value' }); | ||
const value2Node = parse(value2, { context: 'value' }); | ||
if (!(isStandardSyntaxValue(value1) && isStandardSyntaxValue(value2))) { | ||
return false; | ||
} | ||
let value1Node; | ||
let value2Node; | ||
try { | ||
value1Node = parse(value1, { context: 'value' }); | ||
value2Node = parse(value2, { context: 'value' }); | ||
} catch (error) { | ||
return false; | ||
} | ||
const node1Children = hasChildren(value1Node) ? value1Node.children.toArray() : []; | ||
@@ -200,2 +227,5 @@ const node2Children = hasChildren(value2Node) ? value2Node.children.toArray() : []; | ||
// replace previous "active" decl with current one | ||
decls[indexDuplicate] = decl; | ||
return duplicateDecl.remove(); | ||
@@ -202,0 +232,0 @@ }; |
@@ -27,2 +27,53 @@ 'use strict'; | ||
/** @type {Map<string, (decls: Map<string, Declaration>) => (string | undefined)>} */ | ||
const customResolvers = new Map([ | ||
[ | ||
'grid-template', | ||
(decls) => { | ||
const areas = decls.get('grid-template-areas')?.value.trim(); | ||
const columns = decls.get('grid-template-columns')?.value.trim(); | ||
const rows = decls.get('grid-template-rows')?.value.trim(); | ||
if (!(areas && columns && rows)) return; | ||
const splitAreas = [...areas.matchAll(/"[^"]+"/g)].map((x) => x[0]); | ||
const splitRows = rows.split(' '); | ||
if (splitAreas.length === 0 || splitRows.length === 0) return; | ||
if (splitAreas.length !== splitRows.length) return; | ||
const zipped = splitAreas.map((area, i) => `${area} ${splitRows[i]}`).join(' '); | ||
return `${zipped} / ${columns}`; | ||
}, | ||
], | ||
]); | ||
/** | ||
* @param {string} prefixedShorthandProperty | ||
* @param {string[]} prefixedShorthandData | ||
* @param {Map<string, Declaration>} transformedDeclarationNodes | ||
* @returns {string | undefined} | ||
*/ | ||
const resolveShorthandValue = ( | ||
prefixedShorthandProperty, | ||
prefixedShorthandData, | ||
transformedDeclarationNodes, | ||
) => { | ||
const resolver = customResolvers.get(prefixedShorthandProperty); | ||
if (resolver === undefined) { | ||
// the "default" resolver: sort the longhand values in the order | ||
// of their properties | ||
const values = prefixedShorthandData | ||
.map((p) => transformedDeclarationNodes.get(p)?.value.trim()) | ||
.filter(Boolean); | ||
return values.length > 0 ? values.join(' ') : undefined; | ||
} | ||
return resolver(transformedDeclarationNodes); | ||
}; | ||
/** @type {import('stylelint').Rule} */ | ||
@@ -117,17 +168,20 @@ const rule = (primary, secondaryOptions, context) => { | ||
); | ||
const resolvedShorthandValue = prefixedShorthandData | ||
.map((p) => transformedDeclarationNodes.get(p)?.value.trim()) | ||
.filter(Boolean) | ||
.join(' '); | ||
const resolvedShorthandValue = resolveShorthandValue( | ||
prefixedShorthandProperty, | ||
prefixedShorthandData, | ||
transformedDeclarationNodes, | ||
); | ||
const newShorthandDeclarationNode = firstDeclNode.clone({ | ||
prop: prefixedShorthandProperty, | ||
value: resolvedShorthandValue, | ||
}); | ||
if (resolvedShorthandValue) { | ||
const newShorthandDeclarationNode = firstDeclNode.clone({ | ||
prop: prefixedShorthandProperty, | ||
value: resolvedShorthandValue, | ||
}); | ||
firstDeclNode.replaceWith(newShorthandDeclarationNode); | ||
firstDeclNode.replaceWith(newShorthandDeclarationNode); | ||
declNodes.forEach((node) => node.remove()); | ||
declNodes.forEach((node) => node.remove()); | ||
return; | ||
return; | ||
} | ||
} | ||
@@ -134,0 +188,0 @@ } |
@@ -74,2 +74,9 @@ 'use strict'; | ||
/** | ||
* @param {import('postcss-value-parser').Node} node | ||
*/ | ||
function containsOnlyComments(node) { | ||
return 'nodes' in node && node.nodes.every((child) => child.type === 'comment'); | ||
} | ||
/** | ||
* @param {import('postcss').AtRule} atRule | ||
@@ -84,2 +91,8 @@ */ | ||
const [funcNode] = parsed.nodes; | ||
if (funcNode && containsOnlyComments(funcNode)) { | ||
return; | ||
} | ||
if (context.fix) { | ||
@@ -86,0 +99,0 @@ atRule.params = parsed.toString(); |
@@ -32,4 +32,4 @@ 'use strict'; | ||
async function standalone({ | ||
allowEmptyInput = false, | ||
cache: useCache = false, | ||
allowEmptyInput, | ||
cache, | ||
cacheLocation, | ||
@@ -138,3 +138,5 @@ cacheStrategy, | ||
if (fix && postcssResult && !postcssResult.stylelint.ignored) { | ||
const autofix = fix ?? config?.fix ?? false; | ||
if (autofix && postcssResult && !postcssResult.stylelint.ignored) { | ||
returnValue.output = | ||
@@ -169,2 +171,5 @@ !postcssResult.stylelint.disableWritingFix && postcssResult.opts | ||
// do not cache if config is explicitly overrided by option | ||
const useCache = cache ?? config?.cache ?? false; | ||
if (!useCache) { | ||
@@ -252,3 +257,3 @@ stylelint._fileCache.destroy(); | ||
stylelintResults = await Promise.all(getStylelintResults); | ||
} else if (allowEmptyInput) { | ||
} else if (allowEmptyInput ?? config?.allowEmptyInput) { | ||
stylelintResults = await Promise.all([]); | ||
@@ -255,0 +260,0 @@ } else if (filePathsLengthBeforeIgnore) { |
{ | ||
"name": "stylelint", | ||
"version": "15.5.0", | ||
"version": "15.6.0", | ||
"description": "A mighty CSS linter that helps you avoid errors and enforce conventions.", | ||
@@ -115,5 +115,5 @@ "keywords": [ | ||
"dependencies": { | ||
"@csstools/css-parser-algorithms": "^2.1.0", | ||
"@csstools/css-tokenizer": "^2.1.0", | ||
"@csstools/media-query-list-parser": "^2.0.2", | ||
"@csstools/css-parser-algorithms": "^2.1.1", | ||
"@csstools/css-tokenizer": "^2.1.1", | ||
"@csstools/media-query-list-parser": "^2.0.4", | ||
"@csstools/selector-specificity": "^2.2.0", | ||
@@ -143,3 +143,3 @@ "balanced-match": "^2.0.0", | ||
"picocolors": "^1.0.0", | ||
"postcss": "^8.4.21", | ||
"postcss": "^8.4.22", | ||
"postcss-media-query-parser": "^0.2.3", | ||
@@ -146,0 +146,0 @@ "postcss-resolve-nested-selector": "^0.1.1", |
@@ -109,2 +109,5 @@ import type * as PostCSS from 'postcss'; | ||
customSyntax?: CustomSyntax; | ||
allowEmptyInput?: boolean; | ||
cache?: boolean; | ||
fix?: boolean; | ||
}; | ||
@@ -111,0 +114,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
1289573
29121