UNPKG

@adguard/aglint

Version:

Universal adblock filter list linter.

166 lines (163 loc) 6.61 kB
/* * AGLint v3.0.0 (build date: Wed, 21 May 2025 13:24:14 GMT) * (c) 2025 AdGuard Software Ltd. * Released under the MIT license * https://github.com/AdguardTeam/AGLint#readme */ import { walk, generate } from '@adguard/ecss-tree'; import { OPEN_PARENTHESIS, COLON, OPEN_SQUARE_BRACKET, SPACE, CLOSE_SQUARE_BRACKET, COMMA, HASHMARK, DOT, EMPTY, CLOSE_PARENTHESIS } from '../../common/constants.js'; import { CssTreeNodeType } from './css-tree-types.js'; /** * Migrated from https://github.com/AdguardTeam/tsurlfilter/blob/2144959e92f94e4738274ce577c2fe26e3a0c08b/packages/agtree/src/utils/csstree.ts * * We implement our custom generate logic for some CSS nodes because CSSTree's default * generator does not add spaces, just in some edge cases, but we want to show some formatted code. */ /** * Selector generation based on CSSTree's AST. This is necessary because CSSTree * only adds spaces in some edge cases. * * @param selectorNode CSS Tree AST * @returns CSS selector as string */ const generateSelector = (selectorNode) => { let result = EMPTY; let inAttributeSelector = false; let depth = 0; let selectorListDepth = -1; let prevNode = selectorNode; walk(selectorNode, { enter: (node) => { depth += 1; // Skip attribute selector or selector list children if (inAttributeSelector || selectorListDepth > -1) { return; } switch (node.type) { // "Trivial" nodes case CssTreeNodeType.TypeSelector: result += node.name; break; case CssTreeNodeType.ClassSelector: result += DOT; result += node.name; break; case CssTreeNodeType.IdSelector: result += HASHMARK; result += node.name; break; case CssTreeNodeType.Identifier: result += node.name; break; case CssTreeNodeType.Raw: result += node.value; break; // "Advanced" nodes case CssTreeNodeType.Nth: // Default generation enough result += generate(node); break; // For example :not([id], [name]) case CssTreeNodeType.SelectorList: // eslint-disable-next-line no-case-declarations const selectors = []; node.children.forEach((selector) => { if (selector.type === CssTreeNodeType.Selector) { selectors.push(generateSelector(selector)); } else if (selector.type === CssTreeNodeType.Raw) { selectors.push(selector.value); } }); // Join selector lists result += selectors.join(COMMA + SPACE); // Skip nodes here selectorListDepth = depth; break; case CssTreeNodeType.Combinator: if (node.name === SPACE) { result += node.name; break; } // Prevent this case (unnecessary space): has( > .something) if (prevNode.type !== CssTreeNodeType.Selector) { result += SPACE; } result += node.name; result += SPACE; break; case CssTreeNodeType.AttributeSelector: result += OPEN_SQUARE_BRACKET; // Identifier name if (node.name) { result += node.name.name; } // Matcher operator, eg = if (node.matcher) { result += node.matcher; // Value can be String, Identifier or null if (node.value !== null) { // String node if (node.value.type === CssTreeNodeType.String) { result += generate(node.value); } else if (node.value.type === CssTreeNodeType.Identifier) { // Identifier node result += node.value.name; } } } // Flags if (node.flags) { // Space before flags result += SPACE; result += node.flags; } result += CLOSE_SQUARE_BRACKET; inAttributeSelector = true; break; case CssTreeNodeType.PseudoElementSelector: result += COLON; result += COLON; result += node.name; if (node.children !== null) { result += OPEN_PARENTHESIS; } break; case CssTreeNodeType.PseudoClassSelector: result += COLON; result += node.name; if (node.children !== null) { result += OPEN_PARENTHESIS; } break; } prevNode = node; }, leave: (node) => { depth -= 1; if (node.type === CssTreeNodeType.SelectorList && depth + 1 === selectorListDepth) { selectorListDepth = -1; } if (selectorListDepth > -1) { return; } if (node.type === CssTreeNodeType.AttributeSelector) { inAttributeSelector = false; } if (inAttributeSelector) { return; } switch (node.type) { case CssTreeNodeType.PseudoElementSelector: case CssTreeNodeType.PseudoClassSelector: if (node.children !== null) { result += CLOSE_PARENTHESIS; } break; } }, }); return result.trim(); }; export { generateSelector };