first commit
This commit is contained in:
142
node_modules/stylelint/lib/utils/findMediaFeatureNames.mjs
generated
vendored
Normal file
142
node_modules/stylelint/lib/utils/findMediaFeatureNames.mjs
generated
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
import { TokenType, isToken, stringify, tokenize } from '@csstools/css-tokenizer';
|
||||
import {
|
||||
isGeneralEnclosed,
|
||||
isMediaFeature,
|
||||
isMediaQueryInvalid,
|
||||
parseFromTokens,
|
||||
} from '@csstools/media-query-list-parser';
|
||||
import {
|
||||
isSimpleBlockNode,
|
||||
isTokenNode,
|
||||
parseCommaSeparatedListOfComponentValues,
|
||||
} from '@csstools/css-parser-algorithms';
|
||||
|
||||
/** @typedef {Array<import('@csstools/media-query-list-parser').MediaQuery>} MediaQueryList */
|
||||
/** @typedef {import('@csstools/css-tokenizer').TokenIdent} TokenIdent */
|
||||
/** @typedef {{ stringify: () => string }} MediaQuerySerializer */
|
||||
|
||||
const rangeFeatureOperator = /[<>=]/;
|
||||
|
||||
/**
|
||||
* Search a CSS string for Media Feature names.
|
||||
* For every found name, invoke the callback, passing the token
|
||||
* as an argument.
|
||||
*
|
||||
* Found tokens are mutable and modifications made to them will be reflected in the output.
|
||||
*
|
||||
* This function supports some non-standard syntaxes like SCSS variables and interpolation.
|
||||
*
|
||||
* @param {string} mediaQueryParams
|
||||
* @param {(mediaFeatureName: TokenIdent) => void} callback
|
||||
*
|
||||
* @returns {MediaQuerySerializer}
|
||||
*/
|
||||
export default function findMediaFeatureNames(mediaQueryParams, callback) {
|
||||
const tokens = tokenize({ css: mediaQueryParams });
|
||||
const list = parseCommaSeparatedListOfComponentValues(tokens);
|
||||
|
||||
const mediaQueryConditions = list.flatMap((listItem) => {
|
||||
return listItem.flatMap((componentValue) => {
|
||||
if (
|
||||
!isSimpleBlockNode(componentValue) ||
|
||||
componentValue.startToken[0] !== TokenType.OpenParen
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const blockTokens = componentValue.tokens();
|
||||
|
||||
const mediaQueryList = parseFromTokens(blockTokens, {
|
||||
preserveInvalidMediaQueries: true,
|
||||
});
|
||||
|
||||
return mediaQueryList.filter((mediaQuery) => {
|
||||
return !isMediaQueryInvalid(mediaQuery);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
mediaQueryConditions.forEach((mediaQuery) => {
|
||||
mediaQuery.walk(({ node }) => {
|
||||
if (isMediaFeature(node)) {
|
||||
const token = node.getNameToken();
|
||||
|
||||
if (token[0] !== TokenType.Ident) return;
|
||||
|
||||
callback(token);
|
||||
}
|
||||
|
||||
if (isGeneralEnclosed(node)) {
|
||||
topLevelTokenNodes(node).forEach((token, i, topLevelTokens) => {
|
||||
if (token[0] !== TokenType.Ident) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextToken = topLevelTokens[i + 1];
|
||||
const prevToken = topLevelTokens[i - 1];
|
||||
|
||||
if (
|
||||
// Media Feature
|
||||
(!prevToken && nextToken && nextToken[0] === TokenType.Colon) ||
|
||||
// Range Feature
|
||||
(nextToken &&
|
||||
nextToken[0] === TokenType.Delim &&
|
||||
rangeFeatureOperator.test(nextToken[4].value)) ||
|
||||
// Range Feature
|
||||
(prevToken &&
|
||||
prevToken[0] === TokenType.Delim &&
|
||||
rangeFeatureOperator.test(prevToken[4].value))
|
||||
) {
|
||||
callback(token);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Serializing takes time/resources and not all callers will use this.
|
||||
// By returning an object with a stringify method, we can avoid doing
|
||||
// this work when it's not needed.
|
||||
return {
|
||||
stringify() {
|
||||
return stringify(...tokens);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** @param {import('@csstools/media-query-list-parser').GeneralEnclosed} node */
|
||||
function topLevelTokenNodes(node) {
|
||||
const components = node.value.value;
|
||||
|
||||
if (isToken(components) || components.length === 0 || isToken(components[0])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @type {Array<import('@csstools/css-tokenizer').CSSToken>} */
|
||||
const relevantTokens = [];
|
||||
|
||||
// To consume the next token if it is a scss variable
|
||||
let lastWasDollarSign = false;
|
||||
|
||||
components.forEach((component) => {
|
||||
// Only preserve top level tokens (idents, delims, ...)
|
||||
// Discard all blocks, functions, ...
|
||||
if (component && isTokenNode(component)) {
|
||||
if (component.value[0] === TokenType.Delim && component.value[4].value === '$') {
|
||||
lastWasDollarSign = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastWasDollarSign) {
|
||||
lastWasDollarSign = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
relevantTokens.push(component.value);
|
||||
}
|
||||
});
|
||||
|
||||
return relevantTokens;
|
||||
}
|
||||
Reference in New Issue
Block a user