| // NOTICE: This file is generated by Rollup. To modify it, |
| // please instead edit the ESM counterpart and rebuild with Rollup (npm run build). |
| 'use strict'; |
| |
| const cssTokenizer = require('@csstools/css-tokenizer'); |
| const cssParserAlgorithms = require('@csstools/css-parser-algorithms'); |
| const nodeFieldIndices = require('../../utils/nodeFieldIndices.cjs'); |
| const validateTypes = require('../../utils/validateTypes.cjs'); |
| const getAtRuleParams = require('../../utils/getAtRuleParams.cjs'); |
| const getDeclarationValue = require('../../utils/getDeclarationValue.cjs'); |
| const isNonNegativeInteger = require('../../utils/isNonNegativeInteger.cjs'); |
| const matchesStringOrRegExp = require('../../utils/matchesStringOrRegExp.cjs'); |
| const optionsMatches = require('../../utils/optionsMatches.cjs'); |
| const report = require('../../utils/report.cjs'); |
| const ruleMessages = require('../../utils/ruleMessages.cjs'); |
| const validateObjectWithProps = require('../../utils/validateObjectWithProps.cjs'); |
| const validateOptions = require('../../utils/validateOptions.cjs'); |
| |
| const ruleName = 'number-max-precision'; |
| |
| const messages = ruleMessages(ruleName, { |
| expected: (actual, expected) => `Expected "${actual}" to be "${expected}"`, |
| }); |
| |
| const meta = { |
| url: 'https://stylelint.io/user-guide/rules/number-max-precision', |
| }; |
| |
| /** @type {import('stylelint').CoreRules[ruleName]} */ |
| const rule = (primary, secondaryOptions) => { |
| return (root, result) => { |
| const validOptions = validateOptions( |
| result, |
| ruleName, |
| { |
| actual: primary, |
| possible: [isNonNegativeInteger], |
| }, |
| { |
| optional: true, |
| actual: secondaryOptions, |
| possible: { |
| ignoreProperties: [validateTypes.isString, validateTypes.isRegExp], |
| ignoreUnits: [validateTypes.isString, validateTypes.isRegExp], |
| insideFunctions: [validateObjectWithProps(isNonNegativeInteger)], |
| }, |
| }, |
| ); |
| |
| if (!validOptions) { |
| return; |
| } |
| |
| /** @type {Map<string, number>} */ |
| const insideFunctions = new Map(Object.entries(secondaryOptions?.insideFunctions ?? {})); |
| |
| root.walkAtRules((atRule) => { |
| if (atRule.name.toLowerCase() === 'import') { |
| return; |
| } |
| |
| check(atRule, nodeFieldIndices.atRuleParamIndex, getAtRuleParams(atRule)); |
| }); |
| |
| root.walkDecls((decl) => { |
| check(decl, nodeFieldIndices.declarationValueIndex, getDeclarationValue(decl)); |
| }); |
| |
| /** |
| * @template {import('postcss').AtRule | import('postcss').Declaration} T |
| * @param {T} node |
| * @param {(node: T) => number} getIndex |
| * @param {string} value |
| */ |
| function check(node, getIndex, value) { |
| // Get out quickly if there are no periods |
| if (!value.includes('.')) return; |
| |
| const prop = 'prop' in node ? node.prop : undefined; |
| |
| if (optionsMatches(secondaryOptions, 'ignoreProperties', prop)) { |
| return; |
| } |
| |
| const initialState = { |
| ignored: false, |
| precision: primary, |
| }; |
| |
| cssParserAlgorithms.walk( |
| cssParserAlgorithms.parseListOfComponentValues(cssTokenizer.tokenize({ css: value })), |
| ({ node: mediaNode, state }) => { |
| if (!state) return; |
| |
| if (state.ignored) return; |
| |
| walker(node, getIndex, mediaNode, state); |
| }, |
| initialState, |
| ); |
| } |
| |
| /** |
| * @template {import('postcss').AtRule | import('postcss').Declaration} T |
| * @param {T} node |
| * @param {(node: T) => number} getIndex |
| * @param {import('@csstools/css-parser-algorithms').ComponentValue} componentValue |
| * @param {{ ignored: boolean, precision: number }} state |
| */ |
| function walker(node, getIndex, componentValue, state) { |
| if (cssParserAlgorithms.isFunctionNode(componentValue)) { |
| const name = componentValue.getName().toLowerCase(); |
| |
| if (name === 'url') { |
| // postcss-value-parser exposed url token contents as "word" tokens, these were indistinguishable from numeric values in any other function. |
| // With @csstools/css-tokenizer this is no longer relevant, but we preserve the old condition to avoid breaking changes. |
| state.ignored = true; |
| |
| return; |
| } |
| |
| state.precision = precisionInsideFunction(name, state.precision); |
| |
| return; |
| } |
| |
| if (!cssParserAlgorithms.isTokenNode(componentValue)) { |
| return; |
| } |
| |
| const [tokenType, raw, startIndex, endIndex, parsedValue] = componentValue.value; |
| |
| if ( |
| tokenType !== cssTokenizer.TokenType.Number && |
| tokenType !== cssTokenizer.TokenType.Dimension && |
| tokenType !== cssTokenizer.TokenType.Percentage |
| ) { |
| return; |
| } |
| |
| let unitStringLength = 0; |
| |
| if (tokenType === cssTokenizer.TokenType.Dimension) { |
| const unit = parsedValue.unit; |
| |
| unitStringLength = unit.length; |
| |
| if (optionsMatches(secondaryOptions, 'ignoreUnits', unit)) { |
| return; |
| } |
| } else if (tokenType === cssTokenizer.TokenType.Percentage) { |
| unitStringLength = 1; |
| |
| if (optionsMatches(secondaryOptions, 'ignoreUnits', '%')) { |
| return; |
| } |
| } |
| |
| const match = /\d*\.(\d+)/.exec(raw); |
| |
| if (match == null || match[0] == null || match[1] == null) { |
| return; |
| } |
| |
| if (match[1].length <= state.precision) { |
| return; |
| } |
| |
| const nodeIndex = getIndex(node); |
| |
| report({ |
| result, |
| ruleName, |
| node, |
| index: nodeIndex + startIndex, |
| endIndex: nodeIndex + (endIndex + 1) - unitStringLength, |
| message: messages.expected, |
| messageArgs: [parsedValue.value, parsedValue.value.toFixed(state.precision)], |
| }); |
| } |
| |
| /** |
| * @param {string} functionName |
| * @param {number} currentPrecision |
| * @returns {number} |
| */ |
| function precisionInsideFunction(functionName, currentPrecision) { |
| const precisionForFunction = insideFunctions.get(functionName); |
| const hasPrecision = typeof precisionForFunction !== 'undefined'; |
| |
| if (hasPrecision) return precisionForFunction; |
| |
| for (const [name, precision] of insideFunctions) { |
| if (matchesStringOrRegExp(functionName, name)) { |
| return precision; |
| } |
| } |
| |
| return currentPrecision; |
| } |
| }; |
| }; |
| |
| rule.ruleName = ruleName; |
| rule.messages = messages; |
| rule.meta = meta; |
| |
| module.exports = rule; |