UNPKG

less

Version:
1,689 lines (1,503 loc) 493 kB
/** * 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