@lwc/style-compiler
Version:
Transform style sheet to be consumed by the LWC engine
124 lines (109 loc) • 4.39 kB
text/typescript
/*
* 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);
}