UNPKG

@lwc/style-compiler

Version:

Transform style sheet to be consumed by the LWC engine

124 lines (109 loc) 4.39 kB
/* * Copyright (c) 2018, salesforce.com, inc. * All rights reserved. * SPDX-License-Identifier: MIT * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT */ import { Root, isAttribute, isCombinator, isTag, Tag, } from 'postcss-selector-parser'; import { isGlobalAttribute, isAriaAttribute, isDataAttribute, isKnowAttributeOnElement, } from '../utils/html-attributes'; const DEPRECATED_SELECTORS = new Set(['/deep/', '::shadow', '>>>']); const UNSUPPORTED_SELECTORS = new Set(['::slotted', ':root', ':host-context']); function validateSelectors(root: Root) { root.walk(node => { const { value, sourceIndex } = node; if (value) { // Ensure the selector doesn't use a deprecated CSS selector. if (DEPRECATED_SELECTORS.has(value)) { throw root.error( `Invalid usage of deprecated selector "${value}".`, { index: sourceIndex, word: value, }, ); } // Ensure the selector doesn't use an unsupported selector. if (UNSUPPORTED_SELECTORS.has(value)) { throw root.error( `Invalid usage of unsupported selector "${value}".`, { index: sourceIndex, word: value, }, ); } } }); } function validateAttribute(root: Root) { root.walk(node => { if (isAttribute(node)) { const { attribute: attributeName, sourceIndex } = node; // Let's check if the attribute name is either a Global HTML attribute, an ARIA attribute // or a data-* attribute since those are available on all the elements. if (isGlobalAttribute(attributeName) || isAriaAttribute(attributeName) || isDataAttribute(attributeName)) { return; } // If the attribute name is not a globally available attribute, the attribute selector is required // to be associated with a tag selector, so we can validate its usage. Let's walk the compound selector // backward to find the associated tag selector. let tagSelector: Tag | undefined = undefined; let runner = node.prev(); while ( tagSelector === undefined && runner !== undefined && !isCombinator(runner) ) { if (isTag(runner)) { tagSelector = runner; } else { runner = runner.prev(); } } // If the tag selector is not present in the compound selector, we need to warn the user that // the compound selector need to be more specific. if (tagSelector === undefined) { const message = [ `Invalid usage of attribute selector "${attributeName}". `, `For validation purposes, attributes that are not global attributes must be associated `, `with a tag name when used in a CSS selector. (e.g., "input[min]" instead of "[min]")`, ]; throw root.error( message.join(''), { index: sourceIndex, word: attributeName, } ); } // If compound selector is associated with a tag selector, we can validate the usage of the // attribute against the specific tag. const { value: tagName } = tagSelector; if (!isKnowAttributeOnElement(tagName, attributeName)) { const message = [ `Invalid usage of attribute selector "${attributeName}". `, `Attribute "${attributeName}" is not a known attribute on <${tagName}> element.`, ]; throw root.error(message.join(''), { index: sourceIndex, word: attributeName, }); } } }); } export default function validate(root: Root) { validateSelectors(root); validateAttribute(root); }