less
Version:
Leaner CSS
1,689 lines (1,503 loc) • 493 kB
JavaScript
/**
* Less - Leaner CSS v4.6.4
* http://lesscss.org
*
* Copyright (c) 2009-2026, Alexis Sellier <self@cloudhead.net>
* Licensed under the Apache-2.0 License.
*
* @license Apache-2.0
*/
'use strict';
var path = require('path');
var nodeFs = require('fs');
var url = require('url');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
var nodeFs__default = /*#__PURE__*/_interopDefaultLegacy(nodeFs);
var url__default = /*#__PURE__*/_interopDefaultLegacy(url);
function createRequire() { return require; }
const require$6 = createRequire();
class SourceMapGeneratorFallback {
addMapping(){}
setSourceContent(){}
toJSON(){
return null;
}
}
var environment = {
encodeBase64: function encodeBase64(str) {
// Avoid Buffer constructor on newer versions of Node.js.
const buffer = (Buffer.from ? Buffer.from(str) : (new Buffer(str)));
return buffer.toString('base64');
},
mimeLookup: function (filename) {
try {
const mimeModule = require$6('mime');
return mimeModule ? mimeModule.lookup(filename) : "application/octet-stream";
} catch (e) {
return "application/octet-stream";
}
},
charsetLookup: function (mime) {
try {
const mimeModule = require$6('mime');
return mimeModule ? mimeModule.charsets.lookup(mime) : undefined;
} catch (e) {
return undefined;
}
},
getSourceMapGenerator: function getSourceMapGenerator() {
try {
const sourceMapModule = require$6('source-map');
return sourceMapModule ? sourceMapModule.SourceMapGenerator : SourceMapGeneratorFallback;
} catch (e) {
return SourceMapGeneratorFallback;
}
}
};
/** @typedef {import('fs')} FS */
const require$5 = createRequire();
/** @type {FS} */
let fs;
try {
fs = require$5('graceful-fs');
} catch (e) {
fs = nodeFs__default["default"];
}
var fs$1 = fs;
class AbstractFileManager {
getPath(filename) {
let j = filename.lastIndexOf('?');
if (j > 0) {
filename = filename.slice(0, j);
}
j = filename.lastIndexOf('/');
if (j < 0) {
j = filename.lastIndexOf('\\');
}
if (j < 0) {
return '';
}
return filename.slice(0, j + 1);
}
tryAppendExtension(path, ext) {
return /(\.[a-z]*$)|([?;].*)$/.test(path) ? path : path + ext;
}
tryAppendLessExtension(path) {
return this.tryAppendExtension(path, '.less');
}
supportsSync() {
return false;
}
alwaysMakePathsAbsolute() {
return false;
}
isPathAbsolute(filename) {
return (/^(?:[a-z-]+:|\/|\\|#)/i).test(filename);
}
// TODO: pull out / replace?
join(basePath, laterPath) {
if (!basePath) {
return laterPath;
}
return basePath + laterPath;
}
pathDiff(url, baseUrl) {
// diff between two paths to create a relative path
const urlParts = this.extractUrlParts(url);
const baseUrlParts = this.extractUrlParts(baseUrl);
let i;
let max;
let urlDirectories;
let baseUrlDirectories;
let diff = '';
if (urlParts.hostPart !== baseUrlParts.hostPart) {
return '';
}
max = Math.max(baseUrlParts.directories.length, urlParts.directories.length);
for (i = 0; i < max; i++) {
if (baseUrlParts.directories[i] !== urlParts.directories[i]) { break; }
}
baseUrlDirectories = baseUrlParts.directories.slice(i);
urlDirectories = urlParts.directories.slice(i);
for (i = 0; i < baseUrlDirectories.length - 1; i++) {
diff += '../';
}
for (i = 0; i < urlDirectories.length - 1; i++) {
diff += `${urlDirectories[i]}/`;
}
return diff;
}
/**
* Helper function, not part of API.
* This should be replaceable by newer Node / Browser APIs
*
* @param {string} url
* @param {string} baseUrl
*/
extractUrlParts(url, baseUrl) {
// urlParts[1] = protocol://hostname/ OR /
// urlParts[2] = / if path relative to host base
// urlParts[3] = directories
// urlParts[4] = filename
// urlParts[5] = parameters
const urlPartsRegex = /^((?:[a-z-]+:)?\/{2}(?:[^/?#]*\/)|([/\\]))?((?:[^/\\?#]*[/\\])*)([^/\\?#]*)([#?].*)?$/i;
const urlParts = url.match(urlPartsRegex);
const returner = {};
let rawDirectories = [];
const directories = [];
let i;
let baseUrlParts;
if (!urlParts) {
throw new Error(`Could not parse sheet href - '${url}'`);
}
// Stylesheets in IE don't always return the full path
if (baseUrl && (!urlParts[1] || urlParts[2])) {
baseUrlParts = baseUrl.match(urlPartsRegex);
if (!baseUrlParts) {
throw new Error(`Could not parse page url - '${baseUrl}'`);
}
urlParts[1] = urlParts[1] || baseUrlParts[1] || '';
if (!urlParts[2]) {
urlParts[3] = baseUrlParts[3] + urlParts[3];
}
}
if (urlParts[3]) {
rawDirectories = urlParts[3].replace(/\\/g, '/').split('/');
// collapse '..' and skip '.'
for (i = 0; i < rawDirectories.length; i++) {
if (rawDirectories[i] === '..') {
directories.pop();
}
else if (rawDirectories[i] !== '.') {
directories.push(rawDirectories[i]);
}
}
}
returner.hostPart = urlParts[1];
returner.directories = directories;
returner.rawPath = (urlParts[1] || '') + rawDirectories.join('/');
returner.path = (urlParts[1] || '') + directories.join('/');
returner.filename = urlParts[4];
returner.fileUrl = returner.path + (urlParts[4] || '');
returner.url = returner.fileUrl + (urlParts[5] || '');
return returner;
}
}
const require$4 = createRequire();
const FileManager = function() {};
FileManager.prototype = Object.assign(new AbstractFileManager(), {
supports() {
return true;
},
supportsSync() {
return true;
},
loadFile(filename, currentDirectory, options, environment, callback) {
let fullFilename;
const isAbsoluteFilename = this.isPathAbsolute(filename);
const filenamesTried = [];
const self = this;
const prefix = filename.slice(0, 1);
const explicit = prefix === '.' || prefix === '/';
let result = null;
let isNodeModule = false;
const npmPrefix = 'npm://';
options = options || {};
const paths = isAbsoluteFilename ? [''] : [currentDirectory];
if (options.paths) { paths.push.apply(paths, options.paths); }
if (!isAbsoluteFilename && paths.indexOf('.') === -1) { paths.push('.'); }
const prefixes = options.prefixes || [''];
const fileParts = this.extractUrlParts(filename);
if (options.syncImport) {
getFileData(returnData, returnData);
if (callback) {
callback(result.error, result);
}
else {
return result;
}
}
else {
// promise is guaranteed to be asyncronous
// which helps as it allows the file handle
// to be closed before it continues with the next file
return new Promise(getFileData);
}
function returnData(data) {
if (!data.filename) {
result = { error: data };
}
else {
result = data;
}
}
function getFileData(fulfill, reject) {
(function tryPathIndex(i) {
function tryWithExtension() {
const extFilename = options.ext ? self.tryAppendExtension(fullFilename, options.ext) : fullFilename;
if (extFilename !== fullFilename && !explicit && paths[i] === '.') {
try {
fullFilename = require$4.resolve(extFilename);
isNodeModule = true;
}
catch (e) {
filenamesTried.push(npmPrefix + extFilename);
fullFilename = extFilename;
}
}
else {
fullFilename = extFilename;
}
}
if (i < paths.length) {
(function tryPrefix(j) {
if (j < prefixes.length) {
isNodeModule = false;
fullFilename = fileParts.rawPath + prefixes[j] + fileParts.filename;
if (paths[i]) {
if (paths[i].startsWith('#')) {
// Handling paths starting with '#'
fullFilename = paths[i].substr(1) + fullFilename;
}else {
fullFilename = path__default["default"].join(paths[i], fullFilename);
}
}
if (!explicit && paths[i] === '.') {
try {
fullFilename = require$4.resolve(fullFilename);
isNodeModule = true;
}
catch (e) {
filenamesTried.push(npmPrefix + fullFilename);
tryWithExtension();
}
}
else {
tryWithExtension();
}
const readFileArgs = [fullFilename];
if (!options.rawBuffer) {
readFileArgs.push('utf-8');
}
if (options.syncImport) {
try {
const data = fs$1.readFileSync.apply(this, readFileArgs);
fulfill({ contents: data, filename: fullFilename});
}
catch (e) {
filenamesTried.push(isNodeModule ? npmPrefix + fullFilename : fullFilename);
return tryPrefix(j + 1);
}
}
else {
readFileArgs.push(function(e, data) {
if (e) {
filenamesTried.push(isNodeModule ? npmPrefix + fullFilename : fullFilename);
return tryPrefix(j + 1);
}
fulfill({ contents: data, filename: fullFilename});
});
fs$1.readFile.apply(this, readFileArgs);
}
}
else {
tryPathIndex(i + 1);
}
})(0);
} else {
reject({ type: 'File', message: `'${filename}' wasn't found. Tried - ${filenamesTried.join(',')}` });
}
}(0));
}
},
loadFileSync(filename, currentDirectory, options, environment) {
options.syncImport = true;
return this.loadFile(filename, currentDirectory, options, environment);
}
});
var logger = {
error: function(msg) {
this._fireEvent('error', msg);
},
warn: function(msg) {
this._fireEvent('warn', msg);
},
info: function(msg) {
this._fireEvent('info', msg);
},
debug: function(msg) {
this._fireEvent('debug', msg);
},
addListener: function(listener) {
this._listeners.push(listener);
},
removeListener: function(listener) {
for (let i = 0; i < this._listeners.length; i++) {
if (this._listeners[i] === listener) {
this._listeners.splice(i, 1);
return;
}
}
},
_fireEvent: function(type, msg) {
for (let i = 0; i < this._listeners.length; i++) {
const logFunction = this._listeners[i][type];
if (logFunction) {
logFunction(msg);
}
}
},
_listeners: []
};
/* eslint-disable no-unused-vars */
const require$3 = createRequire();
const isUrlRe = /^(?:https?:)?\/\//i;
let request;
const UrlFileManager = function() {};
UrlFileManager.prototype = Object.assign(new AbstractFileManager(), {
supports(filename, currentDirectory, options, environment) {
return isUrlRe.test( filename ) || isUrlRe.test(currentDirectory);
},
loadFile(filename, currentDirectory, options, environment) {
return new Promise((fulfill, reject) => {
if (request === undefined) {
try { request = require$3('needle'); }
catch (e) { request = null; }
}
if (!request) {
reject({ type: 'File', message: 'optional dependency \'needle\' required to import over http(s)\n' });
return;
}
let urlStr = isUrlRe.test( filename ) ? filename : url__default["default"].resolve(currentDirectory, filename);
/** native-request currently has a bug */
const hackUrlStr = urlStr.indexOf('?') === -1 ? urlStr + '?' : urlStr;
request.get(hackUrlStr, { follow_max: 5 }, (err, resp, body) => {
if (err || resp && resp.statusCode >= 400) {
const message = resp && resp.statusCode === 404
? `resource '${urlStr}' was not found\n`
: `resource '${urlStr}' gave this Error:\n ${err || resp.statusMessage || resp.statusCode}\n`;
reject({ type: 'File', message });
return;
}
if (resp.statusCode >= 300) {
reject({ type: 'File', message: `resource '${urlStr}' caused too many redirects` });
return;
}
body = body.toString('utf8');
if (!body) {
logger.warn(`Warning: Empty body (HTTP ${resp.statusCode}) returned by "${urlStr}"`);
}
fulfill({ contents: body || '', filename: urlStr });
});
});
}
});
/**
* @todo Document why this abstraction exists, and the relationship between
* environment, file managers, and plugin manager
*/
class Environment {
constructor(externalEnvironment, fileManagers) {
this.fileManagers = fileManagers || [];
externalEnvironment = externalEnvironment || {};
const optionalFunctions = ['encodeBase64', 'mimeLookup', 'charsetLookup', 'getSourceMapGenerator'];
const requiredFunctions = [];
const functions = requiredFunctions.concat(optionalFunctions);
for (let i = 0; i < functions.length; i++) {
const propName = functions[i];
const environmentFunc = externalEnvironment[propName];
if (environmentFunc) {
this[propName] = environmentFunc.bind(externalEnvironment);
} else if (i < requiredFunctions.length) {
this.warn(`missing required function in environment - ${propName}`);
}
}
}
getFileManager(filename, currentDirectory, options, environment, isSync) {
if (!filename) {
logger.warn('getFileManager called with no filename.. Please report this issue. continuing.');
}
if (currentDirectory === undefined) {
logger.warn('getFileManager called with null directory.. Please report this issue. continuing.');
}
let fileManagers = this.fileManagers;
if (options.pluginManager) {
fileManagers = [].concat(fileManagers).concat(options.pluginManager.getFileManagers());
}
for (let i = fileManagers.length - 1; i >= 0 ; i--) {
const fileManager = fileManagers[i];
if (fileManager[isSync ? 'supportsSync' : 'supports'](filename, currentDirectory, options, environment)) {
return fileManager;
}
}
return null;
}
addFileManager(fileManager) {
this.fileManagers.push(fileManager);
}
clearFileManagers() {
this.fileManagers = [];
}
}
var colors = {
'aliceblue':'#f0f8ff',
'antiquewhite':'#faebd7',
'aqua':'#00ffff',
'aquamarine':'#7fffd4',
'azure':'#f0ffff',
'beige':'#f5f5dc',
'bisque':'#ffe4c4',
'black':'#000000',
'blanchedalmond':'#ffebcd',
'blue':'#0000ff',
'blueviolet':'#8a2be2',
'brown':'#a52a2a',
'burlywood':'#deb887',
'cadetblue':'#5f9ea0',
'chartreuse':'#7fff00',
'chocolate':'#d2691e',
'coral':'#ff7f50',
'cornflowerblue':'#6495ed',
'cornsilk':'#fff8dc',
'crimson':'#dc143c',
'cyan':'#00ffff',
'darkblue':'#00008b',
'darkcyan':'#008b8b',
'darkgoldenrod':'#b8860b',
'darkgray':'#a9a9a9',
'darkgrey':'#a9a9a9',
'darkgreen':'#006400',
'darkkhaki':'#bdb76b',
'darkmagenta':'#8b008b',
'darkolivegreen':'#556b2f',
'darkorange':'#ff8c00',
'darkorchid':'#9932cc',
'darkred':'#8b0000',
'darksalmon':'#e9967a',
'darkseagreen':'#8fbc8f',
'darkslateblue':'#483d8b',
'darkslategray':'#2f4f4f',
'darkslategrey':'#2f4f4f',
'darkturquoise':'#00ced1',
'darkviolet':'#9400d3',
'deeppink':'#ff1493',
'deepskyblue':'#00bfff',
'dimgray':'#696969',
'dimgrey':'#696969',
'dodgerblue':'#1e90ff',
'firebrick':'#b22222',
'floralwhite':'#fffaf0',
'forestgreen':'#228b22',
'fuchsia':'#ff00ff',
'gainsboro':'#dcdcdc',
'ghostwhite':'#f8f8ff',
'gold':'#ffd700',
'goldenrod':'#daa520',
'gray':'#808080',
'grey':'#808080',
'green':'#008000',
'greenyellow':'#adff2f',
'honeydew':'#f0fff0',
'hotpink':'#ff69b4',
'indianred':'#cd5c5c',
'indigo':'#4b0082',
'ivory':'#fffff0',
'khaki':'#f0e68c',
'lavender':'#e6e6fa',
'lavenderblush':'#fff0f5',
'lawngreen':'#7cfc00',
'lemonchiffon':'#fffacd',
'lightblue':'#add8e6',
'lightcoral':'#f08080',
'lightcyan':'#e0ffff',
'lightgoldenrodyellow':'#fafad2',
'lightgray':'#d3d3d3',
'lightgrey':'#d3d3d3',
'lightgreen':'#90ee90',
'lightpink':'#ffb6c1',
'lightsalmon':'#ffa07a',
'lightseagreen':'#20b2aa',
'lightskyblue':'#87cefa',
'lightslategray':'#778899',
'lightslategrey':'#778899',
'lightsteelblue':'#b0c4de',
'lightyellow':'#ffffe0',
'lime':'#00ff00',
'limegreen':'#32cd32',
'linen':'#faf0e6',
'magenta':'#ff00ff',
'maroon':'#800000',
'mediumaquamarine':'#66cdaa',
'mediumblue':'#0000cd',
'mediumorchid':'#ba55d3',
'mediumpurple':'#9370d8',
'mediumseagreen':'#3cb371',
'mediumslateblue':'#7b68ee',
'mediumspringgreen':'#00fa9a',
'mediumturquoise':'#48d1cc',
'mediumvioletred':'#c71585',
'midnightblue':'#191970',
'mintcream':'#f5fffa',
'mistyrose':'#ffe4e1',
'moccasin':'#ffe4b5',
'navajowhite':'#ffdead',
'navy':'#000080',
'oldlace':'#fdf5e6',
'olive':'#808000',
'olivedrab':'#6b8e23',
'orange':'#ffa500',
'orangered':'#ff4500',
'orchid':'#da70d6',
'palegoldenrod':'#eee8aa',
'palegreen':'#98fb98',
'paleturquoise':'#afeeee',
'palevioletred':'#d87093',
'papayawhip':'#ffefd5',
'peachpuff':'#ffdab9',
'peru':'#cd853f',
'pink':'#ffc0cb',
'plum':'#dda0dd',
'powderblue':'#b0e0e6',
'purple':'#800080',
'rebeccapurple':'#663399',
'red':'#ff0000',
'rosybrown':'#bc8f8f',
'royalblue':'#4169e1',
'saddlebrown':'#8b4513',
'salmon':'#fa8072',
'sandybrown':'#f4a460',
'seagreen':'#2e8b57',
'seashell':'#fff5ee',
'sienna':'#a0522d',
'silver':'#c0c0c0',
'skyblue':'#87ceeb',
'slateblue':'#6a5acd',
'slategray':'#708090',
'slategrey':'#708090',
'snow':'#fffafa',
'springgreen':'#00ff7f',
'steelblue':'#4682b4',
'tan':'#d2b48c',
'teal':'#008080',
'thistle':'#d8bfd8',
'tomato':'#ff6347',
'turquoise':'#40e0d0',
'violet':'#ee82ee',
'wheat':'#f5deb3',
'white':'#ffffff',
'whitesmoke':'#f5f5f5',
'yellow':'#ffff00',
'yellowgreen':'#9acd32'
};
var unitConversions = {
length: {
'm': 1,
'cm': 0.01,
'mm': 0.001,
'in': 0.0254,
'px': 0.0254 / 96,
'pt': 0.0254 / 72,
'pc': 0.0254 / 72 * 12
},
duration: {
's': 1,
'ms': 0.001
},
angle: {
'rad': 1 / (2 * Math.PI),
'deg': 1 / 360,
'grad': 1 / 400,
'turn': 1
}
};
var data = { colors, unitConversions };
// @ts-check
/**
* @typedef {object} FileInfo
* @property {string} [filename]
* @property {string} [rootpath]
* @property {string} [currentDirectory]
* @property {string} [rootFilename]
* @property {string} [entryPath]
* @property {boolean} [reference]
*/
/**
* @typedef {object} VisibilityInfo
* @property {number} [visibilityBlocks]
* @property {boolean} [nodeVisible]
*/
/**
* @typedef {object} CSSOutput
* @property {(chunk: string, fileInfo?: FileInfo, index?: number, mapLines?: boolean) => void} add
* @property {() => boolean} isEmpty
*/
/**
* @typedef {object} EvalContext
* @property {number} [numPrecision]
* @property {(op?: string) => boolean} [isMathOn]
* @property {number} [math]
* @property {Node[]} [frames]
* @property {Array<{important?: string}>} [importantScope]
* @property {string[]} [paths]
* @property {boolean} [compress]
* @property {boolean} [strictUnits]
* @property {boolean} [sourceMap]
* @property {boolean} [importMultiple]
* @property {string} [urlArgs]
* @property {boolean} [javascriptEnabled]
* @property {object} [pluginManager]
* @property {number} [rewriteUrls]
* @property {boolean} [inCalc]
* @property {boolean} [mathOn]
* @property {boolean[]} [calcStack]
* @property {boolean[]} [parensStack]
* @property {Node[]} [mediaBlocks]
* @property {Node[]} [mediaPath]
* @property {() => void} [inParenthesis]
* @property {() => void} [outOfParenthesis]
* @property {() => void} [enterCalc]
* @property {() => void} [exitCalc]
* @property {(path: string) => boolean} [pathRequiresRewrite]
* @property {(path: string, rootpath?: string) => string} [rewritePath]
* @property {(path: string) => string} [normalizePath]
* @property {number} [tabLevel]
* @property {boolean} [lastRule]
*/
/**
* @typedef {object} TreeVisitor
* @property {(node: Node) => Node} visit
* @property {(nodes: Node[], nonReplacing?: boolean) => Node[]} visitArray
*/
/**
* The reason why Node is a class and other nodes simply do not extend
* from Node (since we're transpiling) is due to this issue:
*
* @see https://github.com/less/less.js/issues/3434
*/
class Node {
get type() { return ''; }
constructor() {
/** @type {Node | null} */
this.parent = null;
/** @type {number | undefined} */
this.visibilityBlocks = undefined;
/** @type {boolean | undefined} */
this.nodeVisible = undefined;
/** @type {Node | null} */
this.rootNode = null;
/** @type {Node | null} */
this.parsed = null;
/** @type {Node | Node[] | string | number | undefined} */
this.value = undefined;
/** @type {number | undefined} */
this._index = undefined;
/** @type {FileInfo | undefined} */
this._fileInfo = undefined;
}
get currentFileInfo() {
return this.fileInfo();
}
get index() {
return this.getIndex();
}
/**
* @param {Node | Node[]} nodes
* @param {Node} parent
*/
setParent(nodes, parent) {
/** @param {Node} node */
function set(node) {
if (node && node instanceof Node) {
node.parent = parent;
}
}
if (Array.isArray(nodes)) {
nodes.forEach(set);
}
else {
set(nodes);
}
}
/** @returns {number} */
getIndex() {
return this._index || (this.parent && this.parent.getIndex()) || 0;
}
/** @returns {FileInfo} */
fileInfo() {
return this._fileInfo || (this.parent && this.parent.fileInfo()) || {};
}
/** @returns {boolean} */
isRulesetLike() { return false; }
/**
* @param {EvalContext} context
* @returns {string}
*/
toCSS(context) {
/** @type {string[]} */
const strs = [];
this.genCSS(context, {
add: function(chunk, fileInfo, index) {
strs.push(chunk);
},
isEmpty: function () {
return strs.length === 0;
}
});
return strs.join('');
}
/**
* @param {EvalContext} context
* @param {CSSOutput} output
*/
genCSS(context, output) {
output.add(/** @type {string} */ (this.value));
}
/**
* @param {TreeVisitor} visitor
*/
accept(visitor) {
this.value = visitor.visit(/** @type {Node} */ (this.value));
}
/**
* @param {EvalContext} [context]
* @returns {Node}
*/
eval(context) { return this; }
/**
* @param {EvalContext} context
* @param {string} op
* @param {number} a
* @param {number} b
* @returns {number | undefined}
*/
_operate(context, op, a, b) {
switch (op) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/': return a / b;
}
}
/**
* @param {EvalContext} context
* @param {number} value
* @returns {number}
*/
fround(context, value) {
const precision = context && context.numPrecision;
// add "epsilon" to ensure numbers like 1.000000005 (represented as 1.000000004999...) are properly rounded:
return (precision) ? Number((value + 2e-16).toFixed(precision)) : value;
}
/**
* @param {Node & { compare?: (other: Node) => number | undefined }} a
* @param {Node & { compare?: (other: Node) => number | undefined }} b
* @returns {number | undefined}
*/
static compare(a, b) {
/* returns:
-1: a < b
0: a = b
1: a > b
and *any* other value for a != b (e.g. undefined, NaN, -2 etc.) */
if ((a.compare) &&
// for "symmetric results" force toCSS-based comparison
// of Quoted or Anonymous if either value is one of those
!(b.type === 'Quoted' || b.type === 'Anonymous')) {
return a.compare(b);
} else if (b.compare) {
return -b.compare(a);
} else if (a.type !== b.type) {
return undefined;
}
let aVal = a.value;
let bVal = b.value;
if (!Array.isArray(aVal)) {
return aVal === bVal ? 0 : undefined;
}
if (!Array.isArray(bVal)) {
return undefined;
}
if (aVal.length !== bVal.length) {
return undefined;
}
for (let i = 0; i < aVal.length; i++) {
if (Node.compare(aVal[i], bVal[i]) !== 0) {
return undefined;
}
}
return 0;
}
/**
* @param {number | string} a
* @param {number | string} b
* @returns {number | undefined}
*/
static numericCompare(a, b) {
return a < b ? -1
: a === b ? 0
: a > b ? 1 : undefined;
}
/** @returns {boolean} */
blocksVisibility() {
if (this.visibilityBlocks === undefined) {
this.visibilityBlocks = 0;
}
return this.visibilityBlocks !== 0;
}
addVisibilityBlock() {
if (this.visibilityBlocks === undefined) {
this.visibilityBlocks = 0;
}
this.visibilityBlocks = this.visibilityBlocks + 1;
}
removeVisibilityBlock() {
if (this.visibilityBlocks === undefined) {
this.visibilityBlocks = 0;
}
this.visibilityBlocks = this.visibilityBlocks - 1;
}
ensureVisibility() {
this.nodeVisible = true;
}
ensureInvisibility() {
this.nodeVisible = false;
}
/** @returns {boolean | undefined} */
isVisible() {
return this.nodeVisible;
}
/** @returns {VisibilityInfo} */
visibilityInfo() {
return {
visibilityBlocks: this.visibilityBlocks,
nodeVisible: this.nodeVisible
};
}
/** @param {VisibilityInfo} info */
copyVisibilityInfo(info) {
if (!info) {
return;
}
this.visibilityBlocks = info.visibilityBlocks;
this.nodeVisible = info.nodeVisible;
}
}
/**
* Set by the parser at runtime on Node.prototype.
* @type {{ context: EvalContext, importManager: object, imports: object } | undefined}
*/
Node.prototype.parse = undefined;
// @ts-check
/** @import { EvalContext, CSSOutput } from './node.js' */
//
// RGB Colors - #ff0014, #eee
//
class Color extends Node {
get type() { return 'Color'; }
/**
* @param {number[] | string} rgb
* @param {number} [a]
* @param {string} [originalForm]
*/
constructor(rgb, a, originalForm) {
super();
const self = this;
//
// The end goal here, is to parse the arguments
// into an integer triplet, such as `128, 255, 0`
//
// This facilitates operations and conversions.
//
if (Array.isArray(rgb)) {
/** @type {number[]} */
this.rgb = rgb;
} else if (rgb.length >= 6) {
/** @type {number[]} */
this.rgb = [];
/** @type {RegExpMatchArray} */ (rgb.match(/.{2}/g)).map(function (c, i) {
if (i < 3) {
self.rgb.push(parseInt(c, 16));
} else {
self.alpha = (parseInt(c, 16)) / 255;
}
});
} else {
/** @type {number[]} */
this.rgb = [];
rgb.split('').map(function (c, i) {
if (i < 3) {
self.rgb.push(parseInt(c + c, 16));
} else {
self.alpha = (parseInt(c + c, 16)) / 255;
}
});
}
/** @type {number} */
if (typeof this.alpha === 'undefined') {
this.alpha = (typeof a === 'number') ? a : 1;
}
if (typeof originalForm !== 'undefined') {
this.value = originalForm;
}
}
luma() {
let r = this.rgb[0] / 255, g = this.rgb[1] / 255, b = this.rgb[2] / 255;
r = (r <= 0.03928) ? r / 12.92 : Math.pow(((r + 0.055) / 1.055), 2.4);
g = (g <= 0.03928) ? g / 12.92 : Math.pow(((g + 0.055) / 1.055), 2.4);
b = (b <= 0.03928) ? b / 12.92 : Math.pow(((b + 0.055) / 1.055), 2.4);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
/**
* @param {EvalContext} context
* @param {CSSOutput} output
*/
genCSS(context, output) {
output.add(this.toCSS(context));
}
/**
* @param {EvalContext} context
* @param {boolean} [doNotCompress]
* @returns {string}
*/
toCSS(context, doNotCompress) {
const compress = context && context.compress && !doNotCompress;
let color;
let alpha;
/** @type {string | undefined} */
let colorFunction;
/** @type {(string | number)[]} */
let args = [];
// `value` is set if this color was originally
// converted from a named color string so we need
// to respect this and try to output named color too.
alpha = this.fround(context, this.alpha);
if (this.value) {
if (/** @type {string} */ (this.value).indexOf('rgb') === 0) {
if (alpha < 1) {
colorFunction = 'rgba';
}
} else if (/** @type {string} */ (this.value).indexOf('hsl') === 0) {
if (alpha < 1) {
colorFunction = 'hsla';
} else {
colorFunction = 'hsl';
}
} else {
return /** @type {string} */ (this.value);
}
} else {
if (alpha < 1) {
colorFunction = 'rgba';
}
}
switch (colorFunction) {
case 'rgba':
args = this.rgb.map(function (c) {
return clamp$1(Math.round(c), 255);
}).concat(clamp$1(alpha, 1));
break;
case 'hsla':
args.push(clamp$1(alpha, 1));
// eslint-disable-next-line no-fallthrough
case 'hsl':
color = this.toHSL();
args = [
this.fround(context, color.h),
`${this.fround(context, color.s * 100)}%`,
`${this.fround(context, color.l * 100)}%`
].concat(args);
}
if (colorFunction) {
// Values are capped between `0` and `255`, rounded and zero-padded.
return `${colorFunction}(${args.join(`,${compress ? '' : ' '}`)})`;
}
color = this.toRGB();
if (compress) {
const splitcolor = color.split('');
// Convert color to short format
if (splitcolor[1] === splitcolor[2] && splitcolor[3] === splitcolor[4] && splitcolor[5] === splitcolor[6]) {
color = `#${splitcolor[1]}${splitcolor[3]}${splitcolor[5]}`;
}
}
return color;
}
//
// Operations have to be done per-channel, if not,
// channels will spill onto each other. Once we have
// our result, in the form of an integer triplet,
// we create a new Color node to hold the result.
//
/**
* @param {EvalContext} context
* @param {string} op
* @param {Color} other
*/
operate(context, op, other) {
const rgb = new Array(3);
const alpha = this.alpha * (1 - other.alpha) + other.alpha;
for (let c = 0; c < 3; c++) {
rgb[c] = this._operate(context, op, this.rgb[c], other.rgb[c]);
}
return new Color(rgb, alpha);
}
toRGB() {
return toHex(this.rgb);
}
toHSL() {
const r = this.rgb[0] / 255, g = this.rgb[1] / 255, b = this.rgb[2] / 255, a = this.alpha;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
/** @type {number} */
let h;
let s;
const l = (max + min) / 2;
const d = max - min;
if (max === min) {
h = s = 0;
} else {
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
/** @type {number} */ (h) /= 6;
}
return { h: /** @type {number} */ (h) * 360, s, l, a };
}
// Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
toHSV() {
const r = this.rgb[0] / 255, g = this.rgb[1] / 255, b = this.rgb[2] / 255, a = this.alpha;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
/** @type {number} */
let h;
let s;
const v = max;
const d = max - min;
if (max === 0) {
s = 0;
} else {
s = d / max;
}
if (max === min) {
h = 0;
} else {
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
/** @type {number} */ (h) /= 6;
}
return { h: /** @type {number} */ (h) * 360, s, v, a };
}
toARGB() {
return toHex([this.alpha * 255].concat(this.rgb));
}
/**
* @param {Node & { rgb?: number[], alpha?: number }} x
* @returns {0 | undefined}
*/
compare(x) {
return (x.rgb &&
x.rgb[0] === this.rgb[0] &&
x.rgb[1] === this.rgb[1] &&
x.rgb[2] === this.rgb[2] &&
x.alpha === this.alpha) ? 0 : undefined;
}
/** @param {string} keyword */
static fromKeyword(keyword) {
/** @type {Color | undefined} */
let c;
const key = keyword.toLowerCase();
// eslint-disable-next-line no-prototype-builtins
if (colors.hasOwnProperty(key)) {
c = new Color(/** @type {string} */ (colors[/** @type {keyof typeof colors} */ (key)]).slice(1));
}
else if (key === 'transparent') {
c = new Color([0, 0, 0], 0);
}
if (c) {
c.value = keyword;
return c;
}
}
}
/**
* @param {number} v
* @param {number} max
*/
function clamp$1(v, max) {
return Math.min(Math.max(v, 0), max);
}
/** @param {number[]} v */
function toHex(v) {
return `#${v.map(function (c) {
c = clamp$1(Math.round(c), 255);
return (c < 16 ? '0' : '') + c.toString(16);
}).join('')}`;
}
// @ts-check
class Paren extends Node {
get type() { return 'Paren'; }
/** @param {Node} node */
constructor(node) {
super();
this.value = node;
/** @type {boolean | undefined} */
this.noSpacing = undefined;
}
/**
* @param {EvalContext} context
* @param {CSSOutput} output
*/
genCSS(context, output) {
output.add('(');
/** @type {Node} */ (this.value).genCSS(context, output);
output.add(')');
}
/**
* @param {EvalContext} context
* @returns {Paren}
*/
eval(context) {
const paren = new Paren(/** @type {Node} */ (this.value).eval(context));
if (this.noSpacing) {
paren.noSpacing = true;
}
return paren;
}
}
// @ts-check
/** @import { EvalContext, CSSOutput } from './node.js' */
/** @type {Record<string, boolean>} */
const _noSpaceCombinators = {
'': true,
' ': true,
'|': true
};
class Combinator extends Node {
get type() { return 'Combinator'; }
/** @param {string} value */
constructor(value) {
super();
if (value === ' ') {
this.value = ' ';
this.emptyOrWhitespace = true;
} else {
this.value = value ? value.trim() : '';
this.emptyOrWhitespace = this.value === '';
}
}
/**
* @param {EvalContext} context
* @param {CSSOutput} output
*/
genCSS(context, output) {
const spaceOrEmpty = (context.compress || _noSpaceCombinators[/** @type {string} */ (this.value)]) ? '' : ' ';
output.add(spaceOrEmpty + this.value + spaceOrEmpty);
}
}
// @ts-check
class Element extends Node {
get type() { return 'Element'; }
/**
* @param {Combinator | string} combinator
* @param {string | Node} value
* @param {boolean} [isVariable]
* @param {number} [index]
* @param {FileInfo} [currentFileInfo]
* @param {VisibilityInfo} [visibilityInfo]
*/
constructor(combinator, value, isVariable, index, currentFileInfo, visibilityInfo) {
super();
this.combinator = combinator instanceof Combinator ?
combinator : new Combinator(combinator);
if (typeof value === 'string') {
this.value = value.trim();
} else if (value) {
this.value = value;
} else {
this.value = '';
}
/** @type {boolean | undefined} */
this.isVariable = isVariable;
this._index = index;
this._fileInfo = currentFileInfo;
this.copyVisibilityInfo(visibilityInfo);
this.setParent(this.combinator, this);
}
/** @param {TreeVisitor} visitor */
accept(visitor) {
const value = this.value;
this.combinator = /** @type {Combinator} */ (visitor.visit(this.combinator));
if (typeof value === 'object') {
this.value = visitor.visit(/** @type {Node} */ (value));
}
}
/** @param {EvalContext} context */
eval(context) {
return new Element(this.combinator,
/** @type {Node} */ (this.value).eval ? /** @type {Node} */ (this.value).eval(context) : /** @type {string} */ (this.value),
this.isVariable,
this.getIndex(),
this.fileInfo(), this.visibilityInfo());
}
clone() {
return new Element(this.combinator,
/** @type {string | Node} */ (this.value),
this.isVariable,
this.getIndex(),
this.fileInfo(), this.visibilityInfo());
}
/**
* @param {EvalContext} context
* @param {CSSOutput} output
*/
genCSS(context, output) {
output.add(this.toCSS(context), this.fileInfo(), this.getIndex());
}
/** @param {EvalContext} [context] */
toCSS(context) {
/** @type {EvalContext & { firstSelector?: boolean }} */
const ctx = context || {};
let value = this.value;
const firstSelector = ctx.firstSelector;
if (value instanceof Paren) {
// selector in parens should not be affected by outer selector
// flags (breaks only interpolated selectors - see #1973)
ctx.firstSelector = true;
}
value = /** @type {Node} */ (value).toCSS ? /** @type {Node} */ (value).toCSS(ctx) : /** @type {string} */ (value);
ctx.firstSelector = firstSelector;
if (value === '' && this.combinator.value.charAt(0) === '&') {
return '';
} else {
return this.combinator.toCSS(ctx) + value;
}
}
}
const Math$1 = {
ALWAYS: 0,
PARENS_DIVISION: 1,
PARENS: 2
// removed - STRICT_LEGACY: 3
};
const RewriteUrls = {
OFF: 0,
LOCAL: 1,
ALL: 2
};
function getType(payload) {
return Object.prototype.toString.call(payload).slice(8, -1);
}
function isArray(payload) {
return getType(payload) === "Array";
}
function isPlainObject(payload) {
if (getType(payload) !== "Object")
return false;
const prototype = Object.getPrototypeOf(payload);
return !!prototype && prototype.constructor === Object && prototype === Object.prototype;
}
function assignProp(carry, key, newVal, originalObject, includeNonenumerable) {
const propType = {}.propertyIsEnumerable.call(originalObject, key) ? "enumerable" : "nonenumerable";
if (propType === "enumerable")
carry[key] = newVal;
if (includeNonenumerable && propType === "nonenumerable") {
Object.defineProperty(carry, key, {
value: newVal,
enumerable: false,
writable: true,
configurable: true
});
}
}
function copy(target, options = {}) {
if (isArray(target)) {
return target.map((item) => copy(item, options));
}
if (!isPlainObject(target)) {
return target;
}
const props = Object.getOwnPropertyNames(target);
const symbols = Object.getOwnPropertySymbols(target);
return [...props, ...symbols].reduce((carry, key) => {
if (isArray(options.props) && !options.props.includes(key)) {
return carry;
}
const val = target[key];
const newVal = copy(val, options);
assignProp(carry, key, newVal, target, options.nonenumerable);
return carry;
}, {});
}
/* jshint proto: true */
function getLocation(index, inputStream) {
let n = index + 1;
let line = null;
let column = -1;
while (--n >= 0 && inputStream.charAt(n) !== '\n') {
column++;
}
if (typeof index === 'number') {
line = (inputStream.slice(0, index).match(/\n/g) || '').length;
}
return {
line,
column
};
}
function copyArray(arr) {
let i;
const length = arr.length;
const copy = new Array(length);
for (i = 0; i < length; i++) {
copy[i] = arr[i];
}
return copy;
}
function clone(obj) {
const cloned = {};
for (const prop in obj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
cloned[prop] = obj[prop];
}
}
return cloned;
}
function defaults(obj1, obj2) {
let newObj = obj2 || {};
if (!obj2._defaults) {
newObj = {};
const defaults = copy(obj1);
newObj._defaults = defaults;
const cloned = obj2 ? copy(obj2) : {};
Object.assign(newObj, defaults, cloned);
}
return newObj;
}
function copyOptions(obj1, obj2) {
if (obj2 && obj2._defaults) {
return obj2;
}
const opts = defaults(obj1, obj2);
if (opts.strictMath) {
opts.math = Math$1.PARENS;
}
// Back compat with changed relativeUrls option
if (opts.relativeUrls) {
opts.rewriteUrls = RewriteUrls.ALL;
}
if (typeof opts.math === 'string') {
switch (opts.math.toLowerCase()) {
case 'always':
opts.math = Math$1.ALWAYS;
break;
case 'parens-division':
opts.math = Math$1.PARENS_DIVISION;
break;
case 'strict':
case 'parens':
opts.math = Math$1.PARENS;
break;
default:
opts.math = Math$1.PARENS;
}
}
if (typeof opts.rewriteUrls === 'string') {
switch (opts.rewriteUrls.toLowerCase()) {
case 'off':
opts.rewriteUrls = RewriteUrls.OFF;
break;
case 'local':
opts.rewriteUrls = RewriteUrls.LOCAL;
break;
case 'all':
opts.rewriteUrls = RewriteUrls.ALL;
break;
}
}
return opts;
}
function merge(obj1, obj2) {
for (const prop in obj2) {
if (Object.prototype.hasOwnProperty.call(obj2, prop)) {
obj1[prop] = obj2[prop];
}
}
return obj1;
}
function flattenArray(arr, result = []) {
for (let i = 0, length = arr.length; i < length; i++) {
const value = arr[i];
if (Array.isArray(value)) {
flattenArray(value, result);
} else {
if (value !== undefined) {
result.push(value);
}
}
}
return result;
}
function isNullOrUndefined(val) {
return val === null || val === undefined
}
var utils = /*#__PURE__*/Object.freeze({
__proto__: null,
getLocation: getLocation,
copyArray: copyArray,
clone: clone,
defaults: defaults,
copyOptions: copyOptions,
merge: merge,
flattenArray: flattenArray,
isNullOrUndefined: isNullOrUndefined
});
const anonymousFunc = /(<anonymous>|Function):(\d+):(\d+)/;
/**
* This is a centralized class of any error that could be thrown internally (mostly by the parser).
* Besides standard .message it keeps some additional data like a path to the file where the error
* occurred along with line and column numbers.
*
* @class
* @extends Error
* @type {module.LessError}
*
* @prop {string} type
* @prop {string} filename
* @prop {number} index
* @prop {number} line
* @prop {number} column
* @prop {number} callLine
* @prop {number} callExtract
* @prop {string[]} extract
*
* @param {Object} e - An error object to wrap around or just a descriptive object
* @param {Object} fileContentMap - An object with file contents in 'contents' property (like importManager) @todo - move to fileManager?
* @param {string} [currentFilename]
*/
const LessError = function(e, fileContentMap, currentFilename) {
Error.call(this);
const filename = e.filename || currentFilename;
this.message = e.message;
this.stack = e.stack;
// Set type early so it's always available, even if fileContentMap is missing
this.type = e.type || 'Syntax';
if (fileContentMap && filename) {
const input = fileContentMap.contents[filename];
const loc = getLocation(e.index, input);
var line = loc.line;
const col = loc.column;
const callLine = e.call && getLocation(e.call, input).line;
const lines = input ? input.split('\n') : '';
this.filename = filename;
this.index = e.index;
this.line = typeof line === 'number' ? line + 1 : null;
this.column = col;
if (!this.line && this.stack) {
const found = this.stack.match(anonymousFunc);
/**
* We have to figure out how this environment stringifies anonymous functions
* so we can correctly map plugin error