stylelint-no-px
Advanced tools
Comparing version
@@ -1,1 +0,1 @@ | ||
module.exports = require('./lib/') | ||
export {default} from './lib/index.js'; |
224
lib/index.js
@@ -1,106 +0,155 @@ | ||
'use strict' | ||
import stylelint from "stylelint"; | ||
import valueParser from "postcss-value-parser"; | ||
const stylelint = require('stylelint') | ||
const valueParser = require('postcss-value-parser') | ||
const ruleName = 'meowtec/no-px' | ||
export const ruleName = "meowtec/no-px"; | ||
const messages = stylelint.utils.ruleMessages(ruleName, { | ||
rem() { | ||
return `Use rem instead of px` | ||
export const meta = { | ||
fixable: true, | ||
}; | ||
export const messages = stylelint.utils.ruleMessages(ruleName, { | ||
rem(remSize) { | ||
if (remSize) { | ||
return `Use rem instead of px. 'px' values have been converted to 'rem'.`; | ||
} | ||
return `Use rem instead of px. To enable automatic conversion, set the 'remSize' option in the plugin settings.`; | ||
}, | ||
}) | ||
}); | ||
const defaultSecondaryOptions = { | ||
ignore: ['1px'] | ||
ignore: ["1px"], | ||
remSize: null, | ||
}; | ||
function convertPxToRem(pxValue, baseSize) { | ||
const numericValue = parseFloat(pxValue.replace("px", "")); | ||
return `${(numericValue / baseSize).toFixed(5).replace(/\.?0+$/, "")}rem`; | ||
} | ||
const propInIgnoreList = (prop, list) => { | ||
return prop && list.some(item => { | ||
return prop.indexOf(item) > -1 | ||
}) | ||
function propInIgnoreList(prop, list) { | ||
return ( | ||
prop && | ||
list.some((item) => { | ||
return prop.indexOf(item) > -1; | ||
}) | ||
); | ||
} | ||
const propAddXpxInIgnoreList = (prop, list, px) => { | ||
const reg = new RegExp('\\s' + px) | ||
return prop && list.some(item => { | ||
return reg.test(item) && prop.indexOf(item.replace(reg, '')) > -1 | ||
}) | ||
function propAddXpxInIgnoreList(prop, list, px) { | ||
const reg = new RegExp("\\s" + px); | ||
return ( | ||
prop && | ||
list.some((item) => { | ||
return reg.test(item) && prop.indexOf(item.replace(reg, "")) > -1; | ||
}) | ||
); | ||
} | ||
/** | ||
* check if a value has forbidden `px` | ||
*/ | ||
const hasForbiddenPX = (node, options) => { | ||
const type = node.type | ||
const value = type === 'decl' ? node.value : node.params | ||
const prop = type === 'decl' ? node.prop : null | ||
function processValueNodes(nodes, prop, options) { | ||
let hasPx = false; | ||
const parsed = valueParser(value) | ||
let hasPX = false | ||
for (const valueNode of nodes) { | ||
if (valueNode.type === "function") { | ||
if ( | ||
valueNode.value === "url" || | ||
options.ignoreFunctions.indexOf(valueNode.value) > -1 | ||
) { | ||
continue; | ||
} | ||
const ignore = options.ignore || defaultSecondaryOptions.ignore | ||
const ignoreFunctions = options.ignoreFunctions || [] | ||
hasPx ||= processValueNodes(valueNode.nodes, prop, options); | ||
} | ||
if (type === 'atrule' && node.name === 'media') return | ||
if (type === 'decl' && propInIgnoreList(node.prop, ignore)) return | ||
parsed.walk(node => { | ||
// if node is `url(xxx)`, prevent the traversal | ||
let matched; | ||
if ( | ||
node.type === 'function' && | ||
( | ||
node.value === 'url' || | ||
ignoreFunctions.indexOf(node.value) > -1 | ||
) | ||
valueNode.type === "word" && | ||
(matched = valueNode.value.match(/^([-,+]?\d+(\.\d+)?px)$/)) | ||
) { | ||
return false | ||
} | ||
const px = matched[1]; | ||
// console.log('[[[node >>>]]]', node, '[[[<<< node]]]') | ||
let matched | ||
if (node.type === 'word' && (matched = node.value.match(/^([-,+]?\d+(\.\d+)?px)$/))) { | ||
const px = matched[1] | ||
if (px === '0px') { | ||
return | ||
if (px === "0px") { | ||
continue; | ||
} | ||
if ( | ||
!propAddXpxInIgnoreList(prop, ignore, px) && | ||
ignore.indexOf(px) === -1 | ||
!propAddXpxInIgnoreList(prop, options.ignore, px) && | ||
options.ignore.indexOf(px) === -1 | ||
) { | ||
hasPX = true | ||
if (options.remSize) { | ||
valueNode.value = convertPxToRem(px, options.remSize); | ||
} | ||
hasPx = true; | ||
} | ||
} else if (node.type === 'string' && /(@\{[\w-]+\})px\b/.test(node.value)) { | ||
// eg. ~'@{width}px' | ||
hasPX = true | ||
} else if ( | ||
valueNode.type === "string" && | ||
/(@\{[\w-]+\})px\b/.test(valueNode.value) | ||
) { | ||
if (options.remSize) { | ||
valueNode.value = convertPxToRem(valueNode.value, options.remSize); | ||
} | ||
hasPx = true; | ||
} | ||
}) | ||
} | ||
return hasPX | ||
return hasPx; | ||
} | ||
module.exports = stylelint.createPlugin(ruleName, (primaryOption, secondaryOptionObject) => { | ||
primaryOption = primaryOption || '' | ||
function processValue(value, prop, options) { | ||
const parsed = valueParser(value); | ||
const hasPx = processValueNodes(parsed.nodes, prop, options); | ||
return { | ||
newValue: valueParser.stringify(parsed.nodes), | ||
hasPx, | ||
}; | ||
} | ||
function processDeclaration(declaration, options) { | ||
if (propInIgnoreList(declaration.prop, options.ignore)) { | ||
return; | ||
} | ||
const { newValue, hasPx } = processValue( | ||
declaration.value, | ||
declaration.prop, | ||
options, | ||
); | ||
declaration.value = newValue; | ||
return hasPx; | ||
} | ||
function processAtRule(atRule, options) { | ||
if (atRule.type === "atrule" && atRule.name === "media") { | ||
return; | ||
} | ||
const { newValue, hasPx } = processValue(atRule.params, null, options); | ||
atRule.params = newValue; | ||
atRule.value = newValue; | ||
return hasPx; | ||
} | ||
function ruleFunction(primaryOption, secondaryOptionObject, context) { | ||
primaryOption = primaryOption || ""; | ||
return (root, result) => { | ||
if (!primaryOption) return | ||
if (!primaryOption) return; | ||
secondaryOptionObject = secondaryOptionObject || defaultSecondaryOptions | ||
const { | ||
ignore = defaultSecondaryOptions.ignore, | ||
remSize = null, | ||
ignoreFunctions = [], | ||
} = secondaryOptionObject || defaultSecondaryOptions; | ||
// const validOptions = stylelint.utils.validateOptions({ | ||
// ruleName: ruleName, | ||
// result: result, | ||
// actual: primaryOption, | ||
// }) | ||
secondaryOptionObject = { ignore, remSize, ignoreFunctions }; | ||
// if (!validOptions) { | ||
// return | ||
// } | ||
root.walkDecls(declaration => { | ||
if (hasForbiddenPX(declaration, secondaryOptionObject)) { | ||
root.walkDecls((declaration) => { | ||
if ( | ||
processDeclaration(declaration, secondaryOptionObject) && | ||
!context.fix | ||
) { | ||
stylelint.utils.report({ | ||
@@ -110,9 +159,9 @@ ruleName: ruleName, | ||
node: declaration, | ||
message: messages.rem(), | ||
}) | ||
message: messages.rem(secondaryOptionObject.remSize), | ||
}); | ||
} | ||
}) | ||
}); | ||
root.walkAtRules(atRule => { | ||
if (hasForbiddenPX(atRule, secondaryOptionObject)) { | ||
root.walkAtRules((atRule) => { | ||
if (processAtRule(atRule, secondaryOptionObject) && !context.fix) { | ||
stylelint.utils.report({ | ||
@@ -122,10 +171,13 @@ ruleName: ruleName, | ||
node: atRule, | ||
message: messages.rem(), | ||
}) | ||
message: messages.rem(secondaryOptionObject.remSize), | ||
}); | ||
} | ||
}) | ||
} | ||
}) | ||
}); | ||
}; | ||
} | ||
module.exports.ruleName = ruleName | ||
module.exports.messages = messages | ||
ruleFunction.ruleName = ruleName; | ||
ruleFunction.messages = messages; | ||
ruleFunction.meta = meta; | ||
export default stylelint.createPlugin(ruleName, ruleFunction); |
{ | ||
"name": "stylelint-no-px", | ||
"version": "1.0.1", | ||
"version": "2.0.0", | ||
"description": "A stylelint custom rule to ensure using rem instead of px", | ||
"main": "index.js", | ||
"type": "module", | ||
"scripts": { | ||
"test": "tape test" | ||
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js" | ||
}, | ||
@@ -33,12 +34,19 @@ "files": [ | ||
"devDependencies": { | ||
"stylelint": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", | ||
"stylelint-test-rule-tape": "^0.2.0", | ||
"tape": "^4.7.0" | ||
"jest": "^29.7.0", | ||
"jest-preset-stylelint": "^7.0.0", | ||
"prettier": "^3.2.5", | ||
"stylelint": "^16.0.0" | ||
}, | ||
"dependencies": { | ||
"postcss-value-parser": "^3.3.0 || ^4.0.2" | ||
"postcss-less": "^6.0.0", | ||
"postcss-value-parser": "^4.2.0" | ||
}, | ||
"peerDependencies": { | ||
"stylelint": ">= 8" | ||
"stylelint": ">= 16" | ||
}, | ||
"jest": { | ||
"setupFiles": [ | ||
"<rootDir>/jest.setup.js" | ||
] | ||
} | ||
} |
@@ -50,4 +50,8 @@ # stylelint-no-px | ||
ignore check for functions. | ||
Ignore check for functions. | ||
### remSize: number | ||
Specify a base size for converting px to rem. If this option is provided, the plugin will automatically convert pixel values to rem using the provided base size. | ||
### example(1) (the default options) | ||
@@ -103,1 +107,14 @@ | ||
``` | ||
### example(4) | ||
```javascript | ||
// only `border + 1px` is ok | ||
"meowtec/no-px": [true, { "ignore": ["1px"], "remSize": 16 }], | ||
``` | ||
```less | ||
.foo { | ||
height: 16px; // error, auto converts to 1rem | ||
} | ||
``` |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
8631
25.3%150
42.86%119
16.67%Yes
NaN3
50%4
33.33%1
Infinity%+ Added
+ Added
Updated