CasperSecurity
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getFileModifiedMap = getFileModifiedMap;
exports.createContext = createContext;
exports.getContext = getContext;
var _fs = _interopRequireDefault(require("fs"));
var _url = _interopRequireDefault(require("url"));
var _postcss = _interopRequireDefault(require("postcss"));
var _dlv = _interopRequireDefault(require("dlv"));
var _postcssSelectorParser = _interopRequireDefault(require("postcss-selector-parser"));
var _transformThemeValue = _interopRequireDefault(require("../util/transformThemeValue"));
var _parseObjectStyles = _interopRequireDefault(require("../util/parseObjectStyles"));
var _prefixSelector = _interopRequireDefault(require("../util/prefixSelector"));
var _isPlainObject = _interopRequireDefault(require("../util/isPlainObject"));
var _escapeClassName = _interopRequireDefault(require("../util/escapeClassName"));
var _nameClass = _interopRequireWildcard(require("../util/nameClass"));
var _pluginUtils = require("../util/pluginUtils");
var _bigSign = _interopRequireDefault(require("../util/bigSign"));
var _corePlugins = require("../corePlugins");
var sharedState = _interopRequireWildcard(require("./sharedState"));
var _toPath = require("../util/toPath");
var _log = _interopRequireDefault(require("../util/log"));
var _negateValue = _interopRequireDefault(require("../util/negateValue"));
var _isValidArbitraryValue = _interopRequireDefault(require("../util/isValidArbitraryValue"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
function _interopRequireWildcard(obj) {
if (obj && obj.__esModule) {
return obj;
} else {
var newObj = {};
if (obj != null) {
for(var key in obj){
if (Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {};
if (desc.get || desc.set) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
}
newObj.default = obj;
return newObj;
}
}
function parseVariantFormatString(input) {
if (input.includes('{')) {
if (!isBalanced(input)) throw new Error(`Your { and } are unbalanced.`);
return input.split(/{(.*)}/gim).flatMap((line)=>parseVariantFormatString(line)
).filter(Boolean);
}
return [
input.trim()
];
}
function isBalanced(input) {
let count = 0;
for (let char of input){
if (char === '{') {
count++;
} else if (char === '}') {
if (--count < 0) {
return false // unbalanced
;
}
}
}
return count === 0;
}
function insertInto(list, value, { before =[] } = {}) {
before = [].concat(before);
if (before.length <= 0) {
list.push(value);
return;
}
let idx = list.length - 1;
for (let other of before){
let iidx = list.indexOf(other);
if (iidx === -1) continue;
idx = Math.min(idx, iidx);
}
list.splice(idx, 0, value);
}
function parseStyles(styles) {
if (!Array.isArray(styles)) {
return parseStyles([
styles
]);
}
return styles.flatMap((style)=>{
let isNode = !Array.isArray(style) && !(0, _isPlainObject).default(style);
return isNode ? style : (0, _parseObjectStyles).default(style);
});
}
function getClasses(selector) {
let parser = (0, _postcssSelectorParser).default((selectors)=>{
let allClasses = [];
selectors.walkClasses((classNode)=>{
allClasses.push(classNode.value);
});
return allClasses;
});
return parser.transformSync(selector);
}
function extractCandidates(node, state = {
containsNonOnDemandable: false
}, depth = 0) {
let classes = [];
// Handle normal rules
if (node.type === 'rule') {
for (let selector of node.selectors){
let classCandidates = getClasses(selector);
// At least one of the selectors contains non-"on-demandable" candidates.
if (classCandidates.length === 0) {
state.containsNonOnDemandable = true;
}
for (let classCandidate of classCandidates){
classes.push(classCandidate);
}
}
} else if (node.type === 'atrule') {
node.walkRules((rule)=>{
for (let classCandidate of rule.selectors.flatMap((selector)=>getClasses(selector, state, depth + 1)
)){
classes.push(classCandidate);
}
});
}
if (depth === 0) {
return [
state.containsNonOnDemandable || classes.length === 0,
classes
];
}
return classes;
}
function withIdentifiers(styles) {
return parseStyles(styles).flatMap((node)=>{
let nodeMap = new Map();
let [containsNonOnDemandableSelectors, candidates] = extractCandidates(node);
// If this isn't "on-demandable", assign it a universal candidate to always include it.
if (containsNonOnDemandableSelectors) {
candidates.unshift('*');
}
// However, it could be that it also contains "on-demandable" candidates.
// E.g.: `span, .foo {}`, in that case it should still be possible to use
// `@apply foo` for example.
return candidates.map((c)=>{
if (!nodeMap.has(node)) {
nodeMap.set(node, node);
}
return [
c,
nodeMap.get(node)
];
});
});
}
function buildPluginApi(tailwindConfig, context, { variantList , variantMap , offsets , classList }) {
function getConfigValue(path, defaultValue) {
return path ? (0, _dlv).default(tailwindConfig, path, defaultValue) : tailwindConfig;
}
function applyConfiguredPrefix(selector) {
return (0, _prefixSelector).default(tailwindConfig.prefix, selector);
}
function prefixIdentifier(identifier, options) {
if (identifier === '*') {
return '*';
}
if (!options.respectPrefix) {
return identifier;
}
return context.tailwindConfig.prefix + identifier;
}
return {
addVariant (variantName, variantFunctions, options = {}) {
variantFunctions = [].concat(variantFunctions).map((variantFunction)=>{
if (typeof variantFunction !== 'string') {
// Safelist public API functions
return ({ modifySelectors , container , separator })=>{
return variantFunction({
modifySelectors,
container,
separator
});
};
}
variantFunction = variantFunction.replace(/\n+/g, '').replace(/\s{1,}/g, ' ').trim();
let fns = parseVariantFormatString(variantFunction).map((str)=>{
if (!str.startsWith('@')) {
return ({ format })=>format(str)
;
}
let [, name, params] = /@(.*?) (.*)/g.exec(str);
return ({ wrap })=>wrap(_postcss.default.atRule({
name,
params
}))
;
}).reverse();
return (api)=>{
for (let fn of fns){
fn(api);
}
};
});
insertInto(variantList, variantName, options);
variantMap.set(variantName, variantFunctions);
},
postcss: _postcss.default,
prefix: applyConfiguredPrefix,
e: _escapeClassName.default,
config: getConfigValue,
theme (path, defaultValue) {
const [pathRoot, ...subPaths] = (0, _toPath).toPath(path);
const value = getConfigValue([
'theme',
pathRoot,
...subPaths
], defaultValue);
return (0, _transformThemeValue).default(pathRoot)(value);
},
corePlugins: (path)=>{
if (Array.isArray(tailwindConfig.corePlugins)) {
return tailwindConfig.corePlugins.includes(path);
}
return getConfigValue([
'corePlugins',
path
], true);
},
variants: ()=>{
// Preserved for backwards compatibility but not used in v3.0+
return [];
},
addUserCss (userCss) {
for (let [identifier, rule] of withIdentifiers(userCss)){
let offset = offsets.user++;
if (!context.candidateRuleMap.has(identifier)) {
context.candidateRuleMap.set(identifier, []);
}
context.candidateRuleMap.get(identifier).push([
{
sort: offset,
layer: 'user'
},
rule
]);
}
},
addBase (base) {
for (let [identifier, rule] of withIdentifiers(base)){
let prefixedIdentifier = prefixIdentifier(identifier, {});
let offset = offsets.base++;
if (!context.candidateRuleMap.has(prefixedIdentifier)) {
context.candidateRuleMap.set(prefixedIdentifier, []);
}
context.candidateRuleMap.get(prefixedIdentifier).push([
{
sort: offset,
layer: 'base'
},
rule
]);
}
},
/**
* @param {string} group
* @param {Record<string, string | string[]>} declarations
*/ addDefaults (group, declarations) {
const groups = {
[`@defaults ${group}`]: declarations
};
for (let [identifier, rule] of withIdentifiers(groups)){
let prefixedIdentifier = prefixIdentifier(identifier, {});
if (!context.candidateRuleMap.has(prefixedIdentifier)) {
context.candidateRuleMap.set(prefixedIdentifier, []);
}
context.candidateRuleMap.get(prefixedIdentifier).push([
{
sort: offsets.base++,
layer: 'defaults'
},
rule
]);
}
},
addComponents (components, options) {
let defaultOptions = {
respectPrefix: true,
respectImportant: false
};
options = Object.assign({}, defaultOptions, Array.isArray(options) ? {} : options);
for (let [identifier, rule] of withIdentifiers(components)){
let prefixedIdentifier = prefixIdentifier(identifier, options);
classList.add(prefixedIdentifier);
if (!context.candidateRuleMap.has(prefixedIdentifier)) {
context.candidateRuleMap.set(prefixedIdentifier, []);
}
context.candidateRuleMap.get(prefixedIdentifier).push([
{
sort: offsets.components++,
layer: 'components',
options
},
rule
]);
}
},
addUtilities (utilities, options) {
let defaultOptions = {
respectPrefix: true,
respectImportant: true
};
options = Object.assign({}, defaultOptions, Array.isArray(options) ? {} : options);
for (let [identifier, rule] of withIdentifiers(utilities)){
let prefixedIdentifier = prefixIdentifier(identifier, options);
classList.add(prefixedIdentifier);
if (!context.candidateRuleMap.has(prefixedIdentifier)) {
context.candidateRuleMap.set(prefixedIdentifier, []);
}
context.candidateRuleMap.get(prefixedIdentifier).push([
{
sort: offsets.utilities++,
layer: 'utilities',
options
},
rule
]);
}
},
matchUtilities: function(utilities, options) {
let defaultOptions = {
respectPrefix: true,
respectImportant: true
};
options = {
...defaultOptions,
...options
};
let offset = offsets.utilities++;
for(let identifier in utilities){
let prefixedIdentifier = prefixIdentifier(identifier, options);
let rule = utilities[identifier];
classList.add([
prefixedIdentifier,
options
]);
function wrapped(modifier, { isOnlyPlugin }) {
let { type ='any' } = options;
type = [].concat(type);
let [value, coercedType] = (0, _pluginUtils).coerceValue(type, modifier, options, tailwindConfig);
if (value === undefined) {
return [];
}
if (!type.includes(coercedType) && !isOnlyPlugin) {
return [];
}
if (!(0, _isValidArbitraryValue).default(value)) {
return [];
}
let ruleSets = [].concat(rule(value)).filter(Boolean).map((declaration)=>({
[(0, _nameClass).default(identifier, modifier)]: declaration
})
);
return ruleSets;
}
let withOffsets = [
{
sort: offset,
layer: 'utilities',
options
},
wrapped
];
if (!context.candidateRuleMap.has(prefixedIdentifier)) {
context.candidateRuleMap.set(prefixedIdentifier, []);
}
context.candidateRuleMap.get(prefixedIdentifier).push(withOffsets);
}
},
matchComponents: function(components, options) {
let defaultOptions = {
respectPrefix: true,
respectImportant: false
};
options = {
...defaultOptions,
...options
};
let offset = offsets.components++;
for(let identifier in components){
let prefixedIdentifier = prefixIdentifier(identifier, options);
let rule = components[identifier];
classList.add([
prefixedIdentifier,
options
]);
function wrapped(modifier, { isOnlyPlugin }) {
let { type ='any' } = options;
type = [].concat(type);
let [value, coercedType] = (0, _pluginUtils).coerceValue(type, modifier, options, tailwindConfig);
if (value === undefined) {
return [];
}
if (!type.includes(coercedType)) {
if (isOnlyPlugin) {
_log.default.warn([
`Unnecessary typehint \`${coercedType}\` in \`${identifier}-${modifier}\`.`,
`You can safely update it to \`${identifier}-${modifier.replace(coercedType + ':', '')}\`.`,
]);
} else {
return [];
}
}
if (!(0, _isValidArbitraryValue).default(value)) {
return [];
}
let ruleSets = [].concat(rule(value)).filter(Boolean).map((declaration)=>({
[(0, _nameClass).default(identifier, modifier)]: declaration
})
);
return ruleSets;
}
let withOffsets = [
{
sort: offset,
layer: 'components',
options
},
wrapped
];
if (!context.candidateRuleMap.has(prefixedIdentifier)) {
context.candidateRuleMap.set(prefixedIdentifier, []);
}
context.candidateRuleMap.get(prefixedIdentifier).push(withOffsets);
}
}
};
}
let fileModifiedMapCache = new WeakMap();
function getFileModifiedMap(context) {
if (!fileModifiedMapCache.has(context)) {
fileModifiedMapCache.set(context, new Map());
}
return fileModifiedMapCache.get(context);
}
function trackModified(files, fileModifiedMap) {
let changed = false;
for (let file of files){
var ref;
if (!file) continue;
let parsed = _url.default.parse(file);
let pathname = parsed.hash ? parsed.href.replace(parsed.hash, '') : parsed.href;
pathname = parsed.search ? pathname.replace(parsed.search, '') : pathname;
let newModified = (ref = _fs.default.statSync(decodeURIComponent(pathname), {
throwIfNoEntry: false
})) === null || ref === void 0 ? void 0 : ref.mtimeMs;
if (!newModified) {
continue;
}
if (!fileModifiedMap.has(file) || newModified > fileModifiedMap.get(file)) {
changed = true;
}
fileModifiedMap.set(file, newModified);
}
return changed;
}
function extractVariantAtRules(node) {
node.walkAtRules((atRule)=>{
if ([
'responsive',
'variants'
].includes(atRule.name)) {
extractVariantAtRules(atRule);
atRule.before(atRule.nodes);
atRule.remove();
}
});
}
function collectLayerPlugins(root) {
let layerPlugins = [];
root.each((node)=>{
if (node.type === 'atrule' && [
'responsive',
'variants'
].includes(node.name)) {
node.name = 'layer';
node.params = 'utilities';
}
});
// Walk @layer rules and treat them like plugins
root.walkAtRules('layer', (layerRule)=>{
extractVariantAtRules(layerRule);
if (layerRule.params === 'base') {
for (let node of layerRule.nodes){
layerPlugins.push(function({ addBase }) {
addBase(node, {
respectPrefix: false
});
});
}
layerRule.remove();
} else if (layerRule.params === 'components') {
for (let node of layerRule.nodes){
layerPlugins.push(function({ addComponents }) {
addComponents(node, {
respectPrefix: false
});
});
}
layerRule.remove();
} else if (layerRule.params === 'utilities') {
for (let node of layerRule.nodes){
layerPlugins.push(function({ addUtilities }) {
addUtilities(node, {
respectPrefix: false
});
});
}
layerRule.remove();
}
});
root.walkRules((rule)=>{
// At this point it is safe to include all the left-over css from the
// user's css file. This is because the `@tailwind` and `@layer` directives
// will already be handled and will be removed from the css tree.
layerPlugins.push(function({ addUserCss }) {
addUserCss(rule, {
respectPrefix: false
});
});
});
return layerPlugins;
}
function resolvePlugins(context, root) {
let corePluginList = Object.entries({
..._corePlugins.variantPlugins,
..._corePlugins.corePlugins
}).map(([name, plugin])=>{
if (!context.tailwindConfig.corePlugins.includes(name)) {
return null;
}
return plugin;
}).filter(Boolean);
let userPlugins = context.tailwindConfig.plugins.map((plugin)=>{
if (plugin.__isOptionsFunction) {
plugin = plugin();
}
return typeof plugin === 'function' ? plugin : plugin.handler;
});
let layerPlugins = collectLayerPlugins(root);
// TODO: This is a workaround for backwards compatibility, since custom variants
// were historically sorted before screen/stackable variants.
let beforeVariants = [
_corePlugins.variantPlugins['pseudoElementVariants'],
_corePlugins.variantPlugins['pseudoClassVariants'],
];
let afterVariants = [
_corePlugins.variantPlugins['directionVariants'],
_corePlugins.variantPlugins['reducedMotionVariants'],
_corePlugins.variantPlugins['darkVariants'],
_corePlugins.variantPlugins['printVariant'],
_corePlugins.variantPlugins['screenVariants'],
_corePlugins.variantPlugins['orientationVariants'],
];
return [
...corePluginList,
...beforeVariants,
...userPlugins,
...afterVariants,
...layerPlugins
];
}
function registerPlugins(plugins, context) {
let variantList = [];
let variantMap = new Map();
let offsets = {
defaults: 0n,
base: 0n,
components: 0n,
utilities: 0n,
user: 0n
};
let classList = new Set();
let pluginApi = buildPluginApi(context.tailwindConfig, context, {
variantList,
variantMap,
offsets,
classList
});
for (let plugin of plugins){
if (Array.isArray(plugin)) {
for (let pluginItem of plugin){
pluginItem(pluginApi);
}
} else {
plugin === null || plugin === void 0 ? void 0 : plugin(pluginApi);
}
}
let highestOffset = ((args)=>args.reduce((m, e)=>e > m ? e : m
)
)([
offsets.base,
offsets.defaults,
offsets.components,
offsets.utilities,
offsets.user,
]);
let reservedBits = BigInt(highestOffset.toString(2).length);
// A number one less than the top range of the highest offset area
// so arbitrary properties are always sorted at the end.
context.arbitraryPropertiesSort = (1n << reservedBits << 0n) - 1n;
context.layerOrder = {
defaults: 1n << reservedBits << 0n,
base: 1n << reservedBits << 1n,
components: 1n << reservedBits << 2n,
utilities: 1n << reservedBits << 3n,
user: 1n << reservedBits << 4n
};
reservedBits += 5n;
let offset = 0;
context.variantOrder = new Map(variantList.map((variant, i)=>{
let variantFunctions = variantMap.get(variant).length;
let bits = 1n << BigInt(i + offset) << reservedBits;
offset += variantFunctions - 1;
return [
variant,
bits
];
}).sort(([, a], [, z])=>(0, _bigSign).default(a - z)
));
context.minimumScreen = [
...context.variantOrder.values()
].shift();
// Build variantMap
for (let [variantName, variantFunctions1] of variantMap.entries()){
let sort = context.variantOrder.get(variantName);
context.variantMap.set(variantName, variantFunctions1.map((variantFunction, idx)=>[
sort << BigInt(idx),
variantFunction
]
));
}
var _safelist;
let safelist = ((_safelist = context.tailwindConfig.safelist) !== null && _safelist !== void 0 ? _safelist : []).filter(Boolean);
if (safelist.length > 0) {
let checks = [];
for (let value1 of safelist){
if (typeof value1 === 'string') {
context.changedContent.push({
content: value1,
extension: 'html'
});
continue;
}
if (value1 instanceof RegExp) {
_log.default.warn('root-regex', [
'Regular expressions in `safelist` work differently in Tailwind CSS v3.0.',
'Update your `safelist` configuration to eliminate this warning.'
]);
continue;
}
checks.push(value1);
}
if (checks.length > 0) {
let patternMatchingCount = new Map();
for (let util of classList){
let utils = Array.isArray(util) ? (()=>{
let [utilName, options] = util;
var ref;
let classes = Object.keys((ref = options === null || options === void 0 ? void 0 : options.values) !== null && ref !== void 0 ? ref : {}).map((value)=>(0, _nameClass).formatClass(utilName, value)
);
if (options === null || options === void 0 ? void 0 : options.supportsNegativeValues) {
classes = [
...classes,
...classes.map((cls)=>'-' + cls
)
];
}
return classes;
})() : [
util
];
for (let util1 of utils){
for (let { pattern , variants =[] } of checks){
// RegExp with the /g flag are stateful, so let's reset the last
// index pointer to reset the state.
pattern.lastIndex = 0;
if (!patternMatchingCount.has(pattern)) {
patternMatchingCount.set(pattern, 0);
}
if (!pattern.test(util1)) continue;
patternMatchingCount.set(pattern, patternMatchingCount.get(pattern) + 1);
context.changedContent.push({
content: util1,
extension: 'html'
});
for (let variant of variants){
context.changedContent.push({
content: variant + context.tailwindConfig.separator + util1,
extension: 'html'
});
}
}
}
}
for (let [regex, count] of patternMatchingCount.entries()){
if (count !== 0) continue;
_log.default.warn([
`The safelist pattern \`${regex}\` doesn't match any Tailwind CSS classes.`,
'Fix this pattern or remove it from your `safelist` configuration.',
]);
}
}
}
// Generate a list of strings for autocompletion purposes, e.g.
// ['uppercase', 'lowercase', ...]
context.getClassList = function() {
let output = [];
for (let util of classList){
if (Array.isArray(util)) {
let [utilName, options] = util;
let negativeClasses = [];
var ref;
for (let [key, value] of Object.entries((ref = options === null || options === void 0 ? void 0 : options.values) !== null && ref !== void 0 ? ref : {})){
output.push((0, _nameClass).formatClass(utilName, key));
if ((options === null || options === void 0 ? void 0 : options.supportsNegativeValues) && (0, _negateValue).default(value)) {
negativeClasses.push((0, _nameClass).formatClass(utilName, `-${key}`));
}
}
output.push(...negativeClasses);
} else {
output.push(util);
}
}
return output;
};
}
function createContext(tailwindConfig, changedContent = [], root = _postcss.default.root()) {
let context = {
disposables: [],
ruleCache: new Set(),
classCache: new Map(),
applyClassCache: new Map(),
notClassCache: new Set(),
postCssNodeCache: new Map(),
candidateRuleMap: new Map(),
tailwindConfig,
changedContent: changedContent,
variantMap: new Map(),
stylesheetCache: null
};
let resolvedPlugins = resolvePlugins(context, root);
registerPlugins(resolvedPlugins, context);
return context;
}
let contextMap = sharedState.contextMap;
let configContextMap = sharedState.configContextMap;
let contextSourcesMap = sharedState.contextSourcesMap;
function getContext(root, result, tailwindConfig, userConfigPath, tailwindConfigHash, contextDependencies) {
let sourcePath = result.opts.from;
let isConfigFile = userConfigPath !== null;
sharedState.env.DEBUG && console.log('Source path:', sourcePath);
let existingContext;
if (isConfigFile && contextMap.has(sourcePath)) {
existingContext = contextMap.get(sourcePath);
} else if (configContextMap.has(tailwindConfigHash)) {
let context = configContextMap.get(tailwindConfigHash);
contextSourcesMap.get(context).add(sourcePath);
contextMap.set(sourcePath, context);
existingContext = context;
}
// If there's already a context in the cache and we don't need to
// reset the context, return the cached context.
if (existingContext) {
let contextDependenciesChanged = trackModified([
...contextDependencies
], getFileModifiedMap(existingContext));
if (!contextDependenciesChanged) {
return [
existingContext,
false
];
}
}
// If this source is in the context map, get the old context.
// Remove this source from the context sources for the old context,
// and clean up that context if no one else is using it. This can be
// called by many processes in rapid succession, so we check for presence
// first because the first process to run this code will wipe it out first.
if (contextMap.has(sourcePath)) {
let oldContext = contextMap.get(sourcePath);
if (contextSourcesMap.has(oldContext)) {
contextSourcesMap.get(oldContext).delete(sourcePath);
if (contextSourcesMap.get(oldContext).size === 0) {
contextSourcesMap.delete(oldContext);
for (let [tailwindConfigHash, context] of configContextMap){
if (context === oldContext) {
configContextMap.delete(tailwindConfigHash);
}
}
for (let disposable of oldContext.disposables.splice(0)){
disposable(oldContext);
}
}
}
}
sharedState.env.DEBUG && console.log('Setting up new context...');
let context = createContext(tailwindConfig, [], root);
trackModified([
...contextDependencies
], getFileModifiedMap(context));
// ---
// Update all context tracking state
configContextMap.set(tailwindConfigHash, context);
contextMap.set(sourcePath, context);
if (!contextSourcesMap.has(context)) {
contextSourcesMap.set(context, new Set());
}
contextSourcesMap.get(context).add(sourcePath);
return [
context,
true
];
}