Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 1 | 'use strict'; |
| 2 | |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 3 | const declarationValueIndex = require('../utils/declarationValueIndex'); |
Tim van der Lippe | 0a9b84d | 2021-03-24 11:53:15 +0000 | [diff] [blame] | 4 | const getDeclarationValue = require('../utils/getDeclarationValue'); |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 5 | const isStandardSyntaxFunction = require('../utils/isStandardSyntaxFunction'); |
| 6 | const report = require('../utils/report'); |
Tim van der Lippe | 0a9b84d | 2021-03-24 11:53:15 +0000 | [diff] [blame] | 7 | const setDeclarationValue = require('../utils/setDeclarationValue'); |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 8 | const valueParser = require('postcss-value-parser'); |
| 9 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 10 | /** @typedef {import('postcss-value-parser').Node} ValueParserNode */ |
| 11 | /** @typedef {import('postcss-value-parser').DivNode} ValueParserDivNode */ |
| 12 | /** @typedef {(args: { source: string, index: number, err: (message: string) => void }) => void} LocationChecker */ |
| 13 | |
| 14 | /** |
| 15 | * @param {{ |
| 16 | * root: import('postcss').Root, |
| 17 | * locationChecker: LocationChecker, |
| 18 | * fix: ((node: ValueParserDivNode, index: number, nodes: ValueParserNode[]) => boolean) | null, |
| 19 | * result: import('stylelint').PostcssResult, |
| 20 | * checkedRuleName: string, |
| 21 | * }} opts |
| 22 | */ |
| 23 | module.exports = function functionCommaSpaceChecker(opts) { |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 24 | opts.root.walkDecls((decl) => { |
Tim van der Lippe | 0a9b84d | 2021-03-24 11:53:15 +0000 | [diff] [blame] | 25 | const declValue = getDeclarationValue(decl); |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 26 | |
| 27 | let hasFixed; |
| 28 | const parsedValue = valueParser(declValue); |
| 29 | |
| 30 | parsedValue.walk((valueNode) => { |
| 31 | if (valueNode.type !== 'function') { |
| 32 | return; |
| 33 | } |
| 34 | |
| 35 | if (!isStandardSyntaxFunction(valueNode)) { |
| 36 | return; |
| 37 | } |
| 38 | |
| 39 | // Ignore `url()` arguments, which may contain data URIs or other funky stuff |
| 40 | if (valueNode.value.toLowerCase() === 'url') { |
| 41 | return; |
| 42 | } |
| 43 | |
| 44 | const argumentStrings = valueNode.nodes.map((node) => valueParser.stringify(node)); |
| 45 | |
| 46 | const functionArguments = (() => { |
| 47 | // Remove function name and parens |
| 48 | let result = valueNode.before + argumentStrings.join('') + valueNode.after; |
| 49 | |
| 50 | // 1. Remove comments including preceding whitespace (when only succeeded by whitespace) |
| 51 | // 2. Remove all other comments, but leave adjacent whitespace intact |
| 52 | result = result.replace(/( *\/(\*.*\*\/(?!\S)|\/.*)|(\/(\*.*\*\/|\/.*)))/, ''); |
| 53 | |
| 54 | return result; |
| 55 | })(); |
| 56 | |
| 57 | /** |
| 58 | * Gets the index of the comma for checking. |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 59 | * @param {ValueParserDivNode} commaNode The comma node |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 60 | * @param {number} nodeIndex The index of the comma node |
| 61 | * @returns {number} The index of the comma for checking |
| 62 | */ |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 63 | const getCommaCheckIndex = (commaNode, nodeIndex) => { |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 64 | let commaBefore = |
| 65 | valueNode.before + argumentStrings.slice(0, nodeIndex).join('') + commaNode.before; |
| 66 | |
| 67 | // 1. Remove comments including preceding whitespace (when only succeeded by whitespace) |
| 68 | // 2. Remove all other comments, but leave adjacent whitespace intact |
| 69 | commaBefore = commaBefore.replace(/( *\/(\*.*\*\/(?!\S)|\/.*)|(\/(\*.*\*\/|\/.*)))/, ''); |
| 70 | |
| 71 | return commaBefore.length; |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 72 | }; |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 73 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 74 | /** @type {{ commaNode: ValueParserDivNode, checkIndex: number, nodeIndex: number }[]} */ |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 75 | const commaDataList = []; |
| 76 | |
Tim van der Lippe | 4cb0974 | 2022-01-07 14:25:03 +0100 | [diff] [blame^] | 77 | for (const [nodeIndex, node] of valueNode.nodes.entries()) { |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 78 | if (node.type !== 'div' || node.value !== ',') { |
Tim van der Lippe | 4cb0974 | 2022-01-07 14:25:03 +0100 | [diff] [blame^] | 79 | continue; |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 80 | } |
| 81 | |
| 82 | const checkIndex = getCommaCheckIndex(node, nodeIndex); |
| 83 | |
| 84 | commaDataList.push({ |
| 85 | commaNode: node, |
| 86 | checkIndex, |
| 87 | nodeIndex, |
| 88 | }); |
Tim van der Lippe | 4cb0974 | 2022-01-07 14:25:03 +0100 | [diff] [blame^] | 89 | } |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 90 | |
| 91 | for (const { commaNode, checkIndex, nodeIndex } of commaDataList) { |
| 92 | opts.locationChecker({ |
| 93 | source: functionArguments, |
| 94 | index: checkIndex, |
| 95 | err: (message) => { |
| 96 | const index = |
| 97 | declarationValueIndex(decl) + commaNode.sourceIndex + commaNode.before.length; |
| 98 | |
| 99 | if (opts.fix && opts.fix(commaNode, nodeIndex, valueNode.nodes)) { |
| 100 | hasFixed = true; |
| 101 | |
| 102 | return; |
| 103 | } |
| 104 | |
| 105 | report({ |
| 106 | index, |
| 107 | message, |
| 108 | node: decl, |
| 109 | result: opts.result, |
| 110 | ruleName: opts.checkedRuleName, |
| 111 | }); |
| 112 | }, |
| 113 | }); |
| 114 | } |
| 115 | }); |
| 116 | |
| 117 | if (hasFixed) { |
Tim van der Lippe | 0a9b84d | 2021-03-24 11:53:15 +0000 | [diff] [blame] | 118 | setDeclarationValue(decl, parsedValue.toString()); |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 119 | } |
| 120 | }); |
| 121 | }; |