You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

less

Package Overview
Dependencies
Maintainers
5
Versions
143
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

less - npm Package Compare versions

Comparing version
4.4.2
to
4.5.1
+207
scripts/coverage-lines.js
#!/usr/bin/env node
/**
* Generates a line-by-line coverage report showing uncovered lines
* Reads from LCOV format and displays in terminal
* Also outputs JSON file with uncovered lines for programmatic access
*/
const fs = require('fs');
const path = require('path');
const lcovPath = path.join(__dirname, '..', 'coverage', 'lcov.info');
const jsonOutputPath = path.join(__dirname, '..', 'coverage', 'uncovered-lines.json');
if (!fs.existsSync(lcovPath)) {
console.error('LCOV coverage file not found. Run pnpm test:coverage first.');
process.exit(1);
}
const lcovContent = fs.readFileSync(lcovPath, 'utf8');
// Parse LCOV format
const files = [];
let currentFile = null;
const lines = lcovContent.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// SF: source file
if (line.startsWith('SF:')) {
if (currentFile) {
files.push(currentFile);
}
const filePath = line.substring(3);
// Only include src/ files (not less-browser) and bin/
// Exclude abstract base classes (they're meant to be overridden)
const normalized = filePath.replace(/\\/g, '/');
const abstractClasses = ['abstract-file-manager', 'abstract-plugin-loader'];
const isAbstract = abstractClasses.some(abstract => normalized.includes(abstract));
if (!isAbstract &&
((normalized.includes('src/less/') && !normalized.includes('src/less-browser/')) ||
normalized.includes('src/less-node/') ||
normalized.includes('bin/'))) {
// Extract relative path - match src/less/... or src/less-node/... or bin/...
// Path format: src/less/tree/debug-info.js or src/less-node/file-manager.js
// Match from src/ or bin/ to end of path
const match = normalized.match(/(src\/[^/]+\/.+|bin\/.+)$/);
const relativePath = match ? match[1] : (normalized.includes('/src/') || normalized.includes('/bin/') ? normalized.split('/').slice(-3).join('/') : path.basename(filePath));
currentFile = {
path: relativePath,
fullPath: filePath,
uncoveredLines: [],
uncoveredLineCode: {}, // line number -> source code
totalLines: 0,
coveredLines: 0
};
} else {
currentFile = null;
}
}
// DA: line data (line number, execution count)
if (currentFile && line.startsWith('DA:')) {
const match = line.match(/^DA:(\d+),(\d+)$/);
if (match) {
const lineNum = parseInt(match[1], 10);
const count = parseInt(match[2], 10);
currentFile.totalLines++;
if (count > 0) {
currentFile.coveredLines++;
} else {
currentFile.uncoveredLines.push(lineNum);
}
}
}
}
if (currentFile) {
files.push(currentFile);
}
// Read source code for uncovered lines
files.forEach(file => {
if (file.uncoveredLines.length > 0 && fs.existsSync(file.fullPath)) {
try {
const sourceCode = fs.readFileSync(file.fullPath, 'utf8');
const sourceLines = sourceCode.split('\n');
file.uncoveredLines.forEach(lineNum => {
// LCOV uses 1-based line numbers
if (lineNum > 0 && lineNum <= sourceLines.length) {
file.uncoveredLineCode[lineNum] = sourceLines[lineNum - 1].trim();
}
});
} catch (err) {
// If we can't read the source (e.g., it's in lib/ but we want src/), that's ok
// We'll just skip the source code
}
}
});
// Filter to only files with uncovered lines and sort by coverage
const filesWithGaps = files
.filter(f => f.uncoveredLines.length > 0)
.sort((a, b) => {
const aPct = a.totalLines > 0 ? a.coveredLines / a.totalLines : 1;
const bPct = b.totalLines > 0 ? b.coveredLines / b.totalLines : 1;
return aPct - bPct;
});
if (filesWithGaps.length === 0) {
if (files.length === 0) {
console.log('\n⚠️ No source files found in coverage data. This may indicate an issue with the coverage report.\n');
} else {
console.log('\n✅ All analyzed files have 100% line coverage!\n');
console.log(`(Analyzed ${files.length} files from src/less/, src/less-node/, and bin/)\n`);
}
process.exit(0);
}
console.log('\n' + '='.repeat(100));
console.log('Uncovered Lines Report');
console.log('='.repeat(100) + '\n');
filesWithGaps.forEach(file => {
const coveragePct = file.totalLines > 0
? ((file.coveredLines / file.totalLines) * 100).toFixed(1)
: '0.0';
console.log(`\n${file.path} (${coveragePct}% coverage)`);
console.log('-'.repeat(100));
// Group consecutive lines into ranges
const ranges = [];
let start = file.uncoveredLines[0];
let end = file.uncoveredLines[0];
for (let i = 1; i < file.uncoveredLines.length; i++) {
if (file.uncoveredLines[i] === end + 1) {
end = file.uncoveredLines[i];
} else {
ranges.push(start === end ? `${start}` : `${start}..${end}`);
start = file.uncoveredLines[i];
end = file.uncoveredLines[i];
}
}
ranges.push(start === end ? `${start}` : `${start}..${end}`);
// Display ranges (max 5 per line for readability)
const linesPerRow = 5;
for (let i = 0; i < ranges.length; i += linesPerRow) {
const row = ranges.slice(i, i + linesPerRow);
console.log(` Lines: ${row.join(', ')}`);
}
console.log(` Total uncovered: ${file.uncoveredLines.length} of ${file.totalLines} lines`);
});
console.log('\n' + '='.repeat(100) + '\n');
// Write JSON output for programmatic access
const jsonOutput = {
generated: new Date().toISOString(),
files: filesWithGaps.map(file => ({
path: file.path,
fullPath: file.fullPath,
sourcePath: (() => {
// Try to map lib/ path to src/ path
const normalized = file.fullPath.replace(/\\/g, '/');
if (normalized.includes('/lib/')) {
return normalized.replace('/lib/', '/src/').replace(/\.js$/, '.ts');
}
return file.fullPath;
})(),
coveragePercent: file.totalLines > 0
? parseFloat(((file.coveredLines / file.totalLines) * 100).toFixed(1))
: 0,
totalLines: file.totalLines,
coveredLines: file.coveredLines,
uncoveredLines: file.uncoveredLines,
uncoveredLineCode: file.uncoveredLineCode || {},
uncoveredRanges: (() => {
const ranges = [];
if (file.uncoveredLines.length === 0) return ranges;
let start = file.uncoveredLines[0];
let end = file.uncoveredLines[0];
for (let i = 1; i < file.uncoveredLines.length; i++) {
if (file.uncoveredLines[i] === end + 1) {
end = file.uncoveredLines[i];
} else {
ranges.push({ start, end });
start = file.uncoveredLines[i];
end = file.uncoveredLines[i];
}
}
ranges.push({ start, end });
return ranges;
})()
}))
};
fs.writeFileSync(jsonOutputPath, JSON.stringify(jsonOutput, null, 2), 'utf8');
console.log('\n📄 Uncovered lines data written to: coverage/uncovered-lines.json\n');
#!/usr/bin/env node
/**
* Generates a per-file coverage report table for src/ directories
*/
const fs = require('fs');
const path = require('path');
const coverageSummaryPath = path.join(__dirname, '..', 'coverage', 'coverage-summary.json');
if (!fs.existsSync(coverageSummaryPath)) {
console.error('Coverage summary not found. Run pnpm test:coverage first.');
process.exit(1);
}
const coverage = JSON.parse(fs.readFileSync(coverageSummaryPath, 'utf8'));
// Filter to only src/ files (less, less-node) and bin/ files
// Note: src/less-browser/ is excluded because browser tests aren't included in coverage
// Abstract base classes are excluded as they're meant to be overridden by implementations
const abstractClasses = [
'abstract-file-manager',
'abstract-plugin-loader'
];
const srcFiles = Object.entries(coverage)
.filter(([filePath]) => {
const normalized = filePath.replace(/\\/g, '/');
// Exclude abstract classes
if (abstractClasses.some(abstract => normalized.includes(abstract))) {
return false;
}
return (normalized.includes('/src/less/') && !normalized.includes('/src/less-browser/')) ||
normalized.includes('/src/less-node/') ||
normalized.includes('/bin/');
})
.map(([filePath, data]) => {
// Extract relative path from absolute path
const normalized = filePath.replace(/\\/g, '/');
// Match src/ paths or bin/ paths
const match = normalized.match(/((?:src\/[^/]+\/[^/]+\/|bin\/).+)$/);
const relativePath = match ? match[1] : path.basename(filePath);
return {
path: relativePath,
statements: data.statements,
branches: data.branches,
functions: data.functions,
lines: data.lines
};
})
.sort((a, b) => {
// Sort by directory first, then by coverage percentage
const pathCompare = a.path.localeCompare(b.path);
if (pathCompare !== 0) return pathCompare;
return a.statements.pct - b.statements.pct;
});
if (srcFiles.length === 0) {
console.log('No src/ files found in coverage report.');
process.exit(0);
}
// Group by directory
const grouped = {
'src/less/': [],
'src/less-node/': [],
'bin/': []
};
srcFiles.forEach(file => {
if (file.path.startsWith('src/less/')) {
grouped['src/less/'].push(file);
} else if (file.path.startsWith('src/less-node/')) {
grouped['src/less-node/'].push(file);
} else if (file.path.startsWith('bin/')) {
grouped['bin/'].push(file);
}
});
// Print table
console.log('\n' + '='.repeat(100));
console.log('Per-File Coverage Report (src/less/, src/less-node/, and bin/)');
console.log('='.repeat(100));
console.log('For line-by-line coverage details, open coverage/index.html in your browser.');
console.log('='.repeat(100) + '\n');
Object.entries(grouped).forEach(([dir, files]) => {
if (files.length === 0) return;
console.log(`\n${dir.toUpperCase()}`);
console.log('-'.repeat(100));
console.log(
'File'.padEnd(50) +
'Statements'.padStart(12) +
'Branches'.padStart(12) +
'Functions'.padStart(12) +
'Lines'.padStart(12)
);
console.log('-'.repeat(100));
files.forEach(file => {
const filename = file.path.replace(dir, '');
const truncated = filename.length > 48 ? '...' + filename.slice(-45) : filename;
console.log(
truncated.padEnd(50) +
`${file.statements.pct.toFixed(1)}%`.padStart(12) +
`${file.branches.pct.toFixed(1)}%`.padStart(12) +
`${file.functions.pct.toFixed(1)}%`.padStart(12) +
`${file.lines.pct.toFixed(1)}%`.padStart(12)
);
});
// Summary for this directory
const totals = files.reduce((acc, file) => {
acc.statements.total += file.statements.total;
acc.statements.covered += file.statements.covered;
acc.branches.total += file.branches.total;
acc.branches.covered += file.branches.covered;
acc.functions.total += file.functions.total;
acc.functions.covered += file.functions.covered;
acc.lines.total += file.lines.total;
acc.lines.covered += file.lines.covered;
return acc;
}, {
statements: { total: 0, covered: 0 },
branches: { total: 0, covered: 0 },
functions: { total: 0, covered: 0 },
lines: { total: 0, covered: 0 }
});
const stmtPct = totals.statements.total > 0
? (totals.statements.covered / totals.statements.total * 100).toFixed(1)
: '0.0';
const branchPct = totals.branches.total > 0
? (totals.branches.covered / totals.branches.total * 100).toFixed(1)
: '0.0';
const funcPct = totals.functions.total > 0
? (totals.functions.covered / totals.functions.total * 100).toFixed(1)
: '0.0';
const linePct = totals.lines.total > 0
? (totals.lines.covered / totals.lines.total * 100).toFixed(1)
: '0.0';
console.log('-'.repeat(100));
console.log(
'TOTAL'.padEnd(50) +
`${stmtPct}%`.padStart(12) +
`${branchPct}%`.padStart(12) +
`${funcPct}%`.padStart(12) +
`${linePct}%`.padStart(12)
);
});
console.log('\n' + '='.repeat(100) + '\n');
#!/usr/bin/env node
/**
* Post-install script for Less.js package
*
* This script installs Playwright browsers only when:
* 1. This is a development environment (not when installed as a dependency)
* 2. We're in a monorepo context (parent package.json exists)
* 3. Not running in CI or other automated environments
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// Check if we're in a development environment
function isDevelopmentEnvironment() {
// Skip if this is a global install or user config
if (process.env.npm_config_user_config || process.env.npm_config_global) {
return false;
}
// Skip in CI environments
if (process.env.CI || process.env.GITHUB_ACTIONS || process.env.TRAVIS) {
return false;
}
// Check if we're in a monorepo (parent package.json exists)
const parentPackageJson = path.join(__dirname, '../../../package.json');
if (!fs.existsSync(parentPackageJson)) {
return false;
}
// Check if this is the root of the monorepo
const currentPackageJson = path.join(__dirname, '../package.json');
if (!fs.existsSync(currentPackageJson)) {
return false;
}
return true;
}
// Install Playwright browsers
function installPlaywrightBrowsers() {
try {
console.log('🎭 Installing Playwright browsers for development...');
execSync('pnpm exec playwright install', {
stdio: 'inherit',
cwd: path.join(__dirname, '..')
});
console.log('✅ Playwright browsers installed successfully');
} catch (error) {
console.warn('⚠️ Failed to install Playwright browsers:', error.message);
console.warn(' You can install them manually with: pnpm exec playwright install');
}
}
// Main execution
if (isDevelopmentEnvironment()) {
installPlaywrightBrowsers();
}
.test { color: red; }
{"version":3,"sources":["comprehensive.less"],"names":[],"mappings":"AAoBA;EACE,aAAA;EACA,mBAAA;;AAFF,UAIE;EACE,YAAA;EACA,eAAA;;AANJ,UAIE,QAIE;EACE,iBAAA;EACA,mBAAA;;AAVN,UAcE;EACE,mBAAA;EACA,aAAA;;AAhBJ,UAcE,SAIE;EACE,SAAA;EACA,gBAAA;;AAMN;EACE,OAAO,qBAAP;EACA,QAAQ,iBAAR;EACA,YAAA;;AAIF;EACE,cAAA;EACA,mBAAA;EACA,yCAAA;;AAIF;EAlDE,mBAAA;EACA,2BAAA;EACA,wBAAA;EAIA,yCAAA;EA+CA,aAAA;EACA,iBAAA;;AAIF,QAA0B;EACxB;IACE,aAAA;;EADF,UAGE;IACE,eAAA;;;AAKN;EACE;IACE,aAAA;IACA,uBAAuB,cAAvB;IACA,SAAA;;;AAKJ;AAMA;EALE,kBAAA;EACA,YAAA;EACA,eAAA;;AAGF;EAEE,mBAAA;EACA,YAAA;;AAIF,WACE;EACE,gBAAA;;AAFJ,WACE,GAGE;EACE,qBAAA;;AALN,WACE,GAGE,GAGE;EACE,qBAAA;;AAEA,WATN,GAGE,GAGE,EAGG;EACC,cAAA;;AAGF,WAbN,GAGE,GAGE,EAOG;EACC,cAAA;;AAYT;EACC,cAAA;;AAIF,OACE,QACE,QACE;EACE,cAAA","file":"{path}comprehensive.css"}
{"version":3,"sources":["testweb/sourcemaps-basepath.less"],"names":[],"mappings":"AAEA;EACE,eAAA;EACA,gBAAA;;AAGF;EACE,iBAAA","file":"tests-config/sourcemaps-basepath/sourcemaps-basepath.css"}
{"version":3,"sources":["testweb/sourcemaps-include-source.less"],"names":[],"mappings":"AAGA;EACE,mBAAA;EACA,YAAA;EACA,kBAAA;;AAGF;EACE,mBAAA","file":"tests-config/sourcemaps-include-source/sourcemaps-include-source.css","sourcesContent":["@primary: #007bff;\n@secondary: #6c757d;\n\n.button {\n background: @primary;\n color: white;\n padding: 10px 20px;\n}\n\n.secondary {\n background: @secondary;\n}\n\n"]}
{"version":3,"sources":["https://example.com/less/sourcemaps-rootpath.less"],"names":[],"mappings":"AAEA;EACE,aAAA;EACA,YAAA;;AAGF;EACE,WAAA","file":"tests-config/sourcemaps-rootpath/sourcemaps-rootpath.css"}
{"version":3,"sources":["testweb/sourcemaps-url.less"],"names":[],"mappings":"AAEA;EACE,UAAA;EACA,gBAAA;;AAGF;EACE,YAAA","file":"tests-config/sourcemaps-url/sourcemaps-url.css"}
+16
-17

@@ -7,3 +7,3 @@ "use strict";

var testFolder = path.relative(process.cwd(), path.dirname(resolve.sync('@less/test-data')));
var lessFolder = path.join(testFolder, 'less');
var lessFolder = testFolder;

@@ -89,4 +89,3 @@ module.exports = function(grunt) {

"browser",
"no-js-errors",
"legacy"
"no-js-errors"
];

@@ -219,3 +218,3 @@

test: {
command: 'ts-node test/test-es6.ts && node test/index.js'
command: 'npx ts-node test/test-es6.ts && node test/index.js'
},

@@ -236,13 +235,13 @@ generatebrowser: {

// CURRENT OPTIONS
`node bin/lessc --ie-compat ${lessFolder}/_main/lazy-eval.less tmp/lazy-eval.css`,
`node bin/lessc --ie-compat ${lessFolder}/tests-unit/lazy-eval/lazy-eval.less tmp/lazy-eval.css`,
// --math
`node bin/lessc --math=always ${lessFolder}/_main/lazy-eval.less tmp/lazy-eval.css`,
`node bin/lessc --math=parens-division ${lessFolder}/_main/lazy-eval.less tmp/lazy-eval.css`,
`node bin/lessc --math=parens ${lessFolder}/_main/lazy-eval.less tmp/lazy-eval.css`,
`node bin/lessc --math=strict ${lessFolder}/_main/lazy-eval.less tmp/lazy-eval.css`,
`node bin/lessc --math=strict-legacy ${lessFolder}/_main/lazy-eval.less tmp/lazy-eval.css`,
`node bin/lessc --math=always ${lessFolder}/tests-unit/lazy-eval/lazy-eval.less tmp/lazy-eval.css`,
`node bin/lessc --math=parens-division ${lessFolder}/tests-unit/lazy-eval/lazy-eval.less tmp/lazy-eval.css`,
`node bin/lessc --math=parens ${lessFolder}/tests-unit/lazy-eval/lazy-eval.less tmp/lazy-eval.css`,
`node bin/lessc --math=strict ${lessFolder}/tests-unit/lazy-eval/lazy-eval.less tmp/lazy-eval.css`,
`node bin/lessc --math=strict-legacy ${lessFolder}/tests-unit/lazy-eval/lazy-eval.less tmp/lazy-eval.css`,
// DEPRECATED OPTIONS
// --strict-math
`node bin/lessc --strict-math=on ${lessFolder}/_main/lazy-eval.less tmp/lazy-eval.css`
`node bin/lessc --strict-math=on ${lessFolder}/tests-unit/lazy-eval/lazy-eval.less tmp/lazy-eval.css`
].join(" && ")

@@ -252,9 +251,9 @@ },

command: [
`node bin/lessc --clean-css="--s1 --advanced" ${lessFolder}/_main/lazy-eval.less tmp/lazy-eval.css`,
`node bin/lessc --clean-css="--s1 --advanced" ${lessFolder}/tests-unit/lazy-eval/lazy-eval.less tmp/lazy-eval.css`,
"cd lib",
`node ../bin/lessc --clean-css="--s1 --advanced" ../${lessFolder}/_main/lazy-eval.less ../tmp/lazy-eval.css`,
`node ../bin/lessc --source-map=lazy-eval.css.map --autoprefix ../${lessFolder}/_main/lazy-eval.less ../tmp/lazy-eval.css`,
`node ../bin/lessc --clean-css="--s1 --advanced" ../${lessFolder}/tests-unit/lazy-eval/lazy-eval.less ../tmp/lazy-eval.css`,
`node ../bin/lessc --source-map=lazy-eval.css.map --autoprefix ../${lessFolder}/tests-unit/lazy-eval/lazy-eval.less ../tmp/lazy-eval.css`,
"cd ..",
// Test multiple plugins
`node bin/lessc --plugin=clean-css="--s1 --advanced" --plugin=autoprefix="ie 11,Edge >= 13,Chrome >= 47,Firefox >= 45,iOS >= 9.2,Safari >= 9" ${lessFolder}/_main/lazy-eval.less tmp/lazy-eval.css`
`node bin/lessc --plugin=clean-css="--s1 --advanced" --plugin=autoprefix="ie 11,Edge >= 13,Chrome >= 47,Firefox >= 45,iOS >= 9.2,Safari >= 9" ${lessFolder}/tests-unit/lazy-eval/lazy-eval.less tmp/lazy-eval.css`
].join(" && ")

@@ -265,4 +264,4 @@ },

command: [
`node bin/lessc --source-map=test/sourcemaps/maps/import-map.map ${lessFolder}/_main/import.less test/sourcemaps/import.css`,
`node bin/lessc --source-map ${lessFolder}/sourcemaps/basic.less test/sourcemaps/basic.css`
`node bin/lessc --source-map=test/sourcemaps/maps/import-map.map ${lessFolder}/tests-unit/import/import.less test/sourcemaps/import.css`,
`node bin/lessc --source-map ${lessFolder}/tests-config/sourcemaps/basic.less test/sourcemaps/basic.css`
].join(" && ")

@@ -269,0 +268,0 @@ }

@@ -35,3 +35,3 @@ // lessc_helper.js

console.log(' --quiet Suppresses output of warnings.');
console.log(' --strict-imports Forces evaluation of imports.');
console.log(' --strict-imports (DEPRECATED) Ignores .less imports inside selector blocks. Has confusing behavior.');
console.log(' --insecure Allows imports from insecure https hosts.');

@@ -76,8 +76,9 @@ console.log(' -v, --version Prints version number and exit.');

console.log('');
console.log(' --line-numbers=TYPE Outputs filename and line numbers.');
console.log(' TYPE can be either \'comments\', which will output');
console.log(' the debug info within comments, \'mediaquery\'');
console.log(' that will output the information within a fake');
console.log(' media query which is compatible with the SASS');
console.log(' format, and \'all\' which will do both.');
console.log(' --line-numbers=TYPE (DEPRECATED) Outputs filename and line numbers.');
console.log(' TYPE can be either \'comments\', \'mediaquery\', or \'all\'.');
console.log(' The entire dumpLineNumbers option is deprecated.');
console.log(' Use sourcemaps (--source-map) instead.');
console.log(' All modes will be removed in a future version.');
console.log(' Note: \'mediaquery\' and \'all\' modes generate @media -sass-debug-info');
console.log(' which had short-lived usage and is no longer recommended.');
console.log(' -x, --compress Compresses output by removing some whitespaces.');

@@ -84,0 +85,0 @@ console.log(' We recommend you use a dedicated minifer like less-plugin-clean-css');

@@ -30,3 +30,2 @@ "use strict";

'syncImport',
'chunkInput',
'mime',

@@ -33,0 +32,0 @@ 'useFileCache',

@@ -24,5 +24,24 @@ "use strict";

color: true,
/* The strictImports controls whether the compiler will allow an @import inside of either
* @media blocks or (a later addition) other selector blocks.
* See: https://github.com/less/less.js/issues/656 */
/**
* @deprecated This option has confusing behavior and may be removed in a future version.
*
* When true, prevents @import statements for .less files from being evaluated inside
* selector blocks (rulesets). The imports are silently ignored and not output.
*
* Behavior:
* - @import at root level: Always processed
* - @import inside @-rules (@media, @supports, etc.): Processed (these are not selector blocks)
* - @import inside selector blocks (.class, #id, etc.): NOT processed (silently ignored)
*
* When false (default): All @import statements are processed regardless of context.
*
* Note: Despite the name "strict", this option does NOT throw an error when imports
* are used in selector blocks - it silently ignores them. This is confusing
* behavior that may catch users off guard.
*
* Note: Only affects .less file imports. CSS imports (url(...) or .css files) are
* always output as CSS @import statements regardless of this setting.
*
* @see https://github.com/less/less.js/issues/656
*/
strictImports: false,

@@ -29,0 +48,0 @@ /* Allow Imports from Insecure HTTPS Hosts */

@@ -33,2 +33,4 @@ "use strict";

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) {

@@ -41,3 +43,2 @@ var input = fileContentMap.contents[filename];

var lines = input ? input.split('\n') : '';
this.type = e.type || 'Syntax';
this.filename = filename;

@@ -44,0 +45,0 @@ this.index = e.index;

@@ -31,2 +31,3 @@ "use strict";

compress: compress,
// @deprecated The dumpLineNumbers option is deprecated. Use sourcemaps instead. All modes will be removed in a future version.
dumpLineNumbers: options.dumpLineNumbers,

@@ -37,3 +38,59 @@ strictUnits: Boolean(options.strictUnits),

if (options.sourceMap) {
sourceMapBuilder = new SourceMapBuilder(options.sourceMap);
// Normalize sourceMap option: if it's just true, convert to object
if (options.sourceMap === true) {
options.sourceMap = {};
}
var sourceMapOpts = options.sourceMap;
// Set sourceMapInputFilename if not set and filename is available
if (!sourceMapOpts.sourceMapInputFilename && options.filename) {
sourceMapOpts.sourceMapInputFilename = options.filename;
}
// Default sourceMapBasepath to the input file's directory if not set
// This matches the behavior documented and implemented in bin/lessc
if (sourceMapOpts.sourceMapBasepath === undefined && options.filename) {
// Get directory from filename using string manipulation (works cross-platform)
var lastSlash = Math.max(options.filename.lastIndexOf('/'), options.filename.lastIndexOf('\\'));
if (lastSlash >= 0) {
sourceMapOpts.sourceMapBasepath = options.filename.substring(0, lastSlash);
}
else {
// No directory separator found, use current directory
sourceMapOpts.sourceMapBasepath = '.';
}
}
// Handle sourceMapFullFilename (CLI-specific: --source-map=filename)
// This is converted to sourceMapFilename and sourceMapOutputFilename
if (sourceMapOpts.sourceMapFullFilename && !sourceMapOpts.sourceMapFileInline) {
// This case is handled by lessc before calling render
// We just need to ensure sourceMapFilename is set if sourceMapFullFilename is provided
if (!sourceMapOpts.sourceMapFilename && !sourceMapOpts.sourceMapURL) {
// Extract just the basename for the sourceMappingURL comment
var mapBase = sourceMapOpts.sourceMapFullFilename.split(/[/\\]/).pop();
sourceMapOpts.sourceMapFilename = mapBase;
}
}
else if (!sourceMapOpts.sourceMapFilename && !sourceMapOpts.sourceMapURL) {
// If sourceMapFilename is not set and sourceMapURL is not set,
// derive it from the output filename (if available) or input filename
if (sourceMapOpts.sourceMapOutputFilename) {
// Use output filename + .map
sourceMapOpts.sourceMapFilename = sourceMapOpts.sourceMapOutputFilename + '.map';
}
else if (options.filename) {
// Fallback to input filename + .css.map
var inputBase = options.filename.replace(/\.[^/.]+$/, '');
sourceMapOpts.sourceMapFilename = inputBase + '.css.map';
}
}
// Default sourceMapOutputFilename if not set
if (!sourceMapOpts.sourceMapOutputFilename) {
if (options.filename) {
var inputBase = options.filename.replace(/\.[^/.]+$/, '');
sourceMapOpts.sourceMapOutputFilename = inputBase + '.css';
}
else {
sourceMapOpts.sourceMapOutputFilename = 'output.css';
}
}
sourceMapBuilder = new SourceMapBuilder(sourceMapOpts);
result.css = sourceMapBuilder.toCSS(evaldRoot, toCSSOptions, this.imports);

@@ -40,0 +97,0 @@ }

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var chunker_1 = tslib_1.__importDefault(require("./chunker"));
exports.default = (function () {

@@ -320,21 +318,6 @@ var // Less input string

};
parserInput.start = function (str, chunkInput, failFunction) {
parserInput.start = function (str) {
input = str;
parserInput.i = j = currentPos = furthest = 0;
// chunking apparently makes things quicker (but my tests indicate
// it might actually make things slower in node at least)
// and it is a non-perfect parse - it can't recognise
// unquoted urls, meaning it can't distinguish comments
// meaning comments with quotes or {}() in them get 'counted'
// and then lead to parse errors.
// In addition if the chunking chunks in the wrong place we might
// not be able to parse a parser statement in one go
// this is officially deprecated but can be switched on via an option
// in the case it causes too much performance issues.
if (chunkInput) {
chunks = (0, chunker_1.default)(str, failFunction);
}
else {
chunks = [str];
}
chunks = [str];
current = chunks[0];

@@ -341,0 +324,0 @@ skipWhitespace(0);

@@ -13,3 +13,3 @@ "use strict";

}
this._outputFilename = options.outputFilename;
this._outputFilename = options.outputFilename ? options.outputFilename.replace(/\\/g, '/') : options.outputFilename;
this.sourceMapURL = options.sourceMapURL;

@@ -16,0 +16,0 @@ if (options.sourceMapBasepath) {

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* @deprecated The dumpLineNumbers option is deprecated. Use sourcemaps instead.
* This will be removed in a future version.
*
* @param {Object} ctx - Context object with debugInfo
* @returns {string} Debug info as CSS comment
*/
function asComment(ctx) {
return "/* line ".concat(ctx.debugInfo.lineNumber, ", ").concat(ctx.debugInfo.fileName, " */\n");
}
/**
* @deprecated The dumpLineNumbers option is deprecated. Use sourcemaps instead.
* This function generates Sass-compatible debug info using @media -sass-debug-info syntax.
* This format had short-lived usage and is no longer recommended.
* This will be removed in a future version.
*
* @param {Object} ctx - Context object with debugInfo
* @returns {string} Sass-compatible debug info as @media query
*/
function asMediaQuery(ctx) {

@@ -18,2 +34,15 @@ var filenameWithProtocol = ctx.debugInfo.fileName;

}
/**
* Generates debug information (line numbers) for CSS output.
*
* @param {Object} context - Context object with dumpLineNumbers option
* @param {Object} ctx - Context object with debugInfo
* @param {string} [lineSeparator] - Separator between comment and media query (for 'all' mode)
* @returns {string} Debug info string
*
* @deprecated The dumpLineNumbers option is deprecated. Use sourcemaps instead.
* All modes ('comments', 'mediaquery', 'all') are deprecated and will be removed in a future version.
* The 'mediaquery' and 'all' modes generate Sass-compatible @media -sass-debug-info output
* which had short-lived usage and is no longer recommended.
*/
function debugInfo(context, ctx, lineSeparator) {

@@ -20,0 +49,0 @@ var result = '';

{
"name": "less",
"version": "4.4.2",
"version": "4.5.1",
"description": "Leaner CSS",

@@ -40,2 +40,3 @@ "homepage": "http://lesscss.org",

"test": "grunt test",
"test:coverage": "c8 -r lcov -r json-summary -r text-summary -r html --include=\"lib/**/*.js\" --include=\"bin/**/*.js\" --exclude=\"dist/**\" --exclude=\"**/*.test.js\" --exclude=\"**/*.spec.js\" --exclude=\"test/**\" --exclude=\"tmp/**\" --exclude=\"**/abstract-file-manager.js\" --exclude=\"**/abstract-plugin-loader.js\" grunt shell:test && node scripts/coverage-report.js && node scripts/coverage-lines.js",
"grunt": "grunt",

@@ -48,3 +49,4 @@ "lint": "eslint '**/*.{ts,js}'",

"dev": "tsc -p tsconfig.build.json -w",
"prepublishOnly": "grunt dist"
"prepublishOnly": "grunt dist",
"postinstall": "node scripts/postinstall.js"
},

@@ -71,7 +73,10 @@ "optionalDependencies": {

"chai": "^4.2.0",
"c8": "^10.1.3",
"chalk": "^4.1.2",
"cosmiconfig": "~9.0.0",
"cross-env": "^7.0.3",
"diff": "^3.2.0",
"eslint": "^7.29.0",
"fs-extra": "^8.1.0",
"git-rev": "^0.2.1",
"glob": "~11.0.3",
"globby": "^10.0.1",

@@ -86,2 +91,3 @@ "grunt": "^1.0.4",

"html-template-tag": "^3.2.0",
"jest-diff": "~30.1.2",
"jit-grunt": "^0.10.0",

@@ -92,8 +98,7 @@ "less-plugin-autoprefix": "^1.5.1",

"mocha": "^6.2.1",
"playwright": "1.50.1",
"mocha-teamcity-reporter": "^3.0.0",
"nock": "^11.8.2",
"npm-run-all": "^4.1.5",
"performance-now": "^0.2.0",
"phin": "^2.2.3",
"playwright": "1.50.1",
"promise": "^7.1.1",

@@ -100,0 +105,0 @@ "read-glob": "^3.0.0",

@@ -102,2 +102,14 @@ var logMessages = [];

}
// Normalize URLs: convert absolute URLs back to relative for comparison
// The browser resolves relative URLs when reading from DOM, but we want to compare against the original relative URLs
lessOutput = lessOutput.replace(/url\("http:\/\/localhost:8081\/packages\/less\/node_modules\/@less\/test-data\/tests-unit\/([^"]+)"\)/g, 'url("$1")');
// Also normalize directory-prefixed relative URLs (e.g., "at-rules/myfont.woff2" -> "myfont.woff2")
// This happens because the browser resolves URLs relative to the HTML document location
lessOutput = lessOutput.replace(/url\("([a-z-]+\/)([^"]+)"\)/g, 'url("$2")');
// Also normalize @import statements that get resolved to absolute URLs
lessOutput = lessOutput.replace(/@import "http:\/\/localhost:8081\/packages\/less\/node_modules\/@less\/test-data\/tests-unit\/([^"]+)"(.*);/g, '@import "$1"$2;');
// Also normalize @import with directory prefix (e.g., "at-rules-keyword-comments/test.css" -> "test.css")
lessOutput = lessOutput.replace(/@import "([a-z-]+\/)([^"]+)"(.*);/g, '@import "$2"$3;');
expect(lessOutput).to.equal(text);

@@ -168,2 +180,11 @@ done();

.trim();
actualErrorMsg = actualErrorMsg
.replace(/ in [\w\-]+\.less( on line \d+, column \d+)?:?$/, '') // Remove filename and optional line/column from end of error message
.replace(/\{path\}/g, '')
.replace(/\{pathrel\}/g, '')
.replace(/\{pathhref\}/g, 'http://localhost:8081/packages/less/node_modules/@less/test-data/tests-error/eval/')
.replace(/\{404status\}/g, ' (404)')
.replace(/\{node\}[\s\S]*\{\/node\}/g, '')
.replace(/\n$/, '')
.trim();
errorFile

@@ -174,3 +195,3 @@ .then(function (errorTxt) {

.replace(/\{pathrel\}/g, '')
.replace(/\{pathhref\}/g, 'http://localhost:8081/test/less/errors/')
.replace(/\{pathhref\}/g, 'http://localhost:8081/packages/less/node_modules/@less/test-data/tests-error/eval/')
.replace(/\{404status\}/g, ' (404)')

@@ -177,0 +198,0 @@ .replace(/\{node\}[\s\S]*\{\/node\}/g, '')

@@ -7,3 +7,4 @@ var path = require('path');

var testFolder = forceCovertToBrowserPath(path.dirname(resolve.sync('@less/test-data')));
var lessFolder = forceCovertToBrowserPath(path.join(testFolder, 'less'));
var testsUnitFolder = forceCovertToBrowserPath(path.join(testFolder, 'tests-unit'));
var testsConfigFolder = forceCovertToBrowserPath(path.join(testFolder, 'tests-config'));
var localTests = forceCovertToBrowserPath(path.resolve(__dirname, '..'));

@@ -15,10 +16,13 @@

src: [
`${lessFolder}/_main/*.less`,
`!${lessFolder}/_main/plugin-preeval.less`, // uses ES6 syntax
`${testsUnitFolder}/*/*.less`,
`!${testsUnitFolder}/plugin-preeval/plugin-preeval.less`, // uses ES6 syntax
// Don't test NPM import, obviously
`!${lessFolder}/_main/plugin-module.less`,
`!${lessFolder}/_main/import-module.less`,
`!${lessFolder}/_main/javascript.less`,
`!${lessFolder}/_main/urls.less`,
`!${lessFolder}/_main/empty.less`
`!${testsUnitFolder}/plugin-module/plugin-module.less`,
`!${testsUnitFolder}/import/import-module.less`,
`!${testsUnitFolder}/javascript/javascript.less`,
`!${testsUnitFolder}/urls/urls.less`,
`!${testsUnitFolder}/empty/empty.less`,
`!${testsUnitFolder}/color-functions/operations.less`, // conflicts with operations/operations.less
// Exclude debug line numbers tests - these are Node.js only (dumpLineNumbers is deprecated)
`!${testsConfigFolder}/debug/**/*.less`
],

@@ -31,12 +35,4 @@ options: {

},
legacy: {
src: [`${lessFolder}/legacy/*.less`],
options: {
helpers: 'test/browser/runner-legacy-options.js',
specs: 'test/browser/runner-legacy-spec.js',
outfile: 'tmp/browser/test-runner-legacy.html'
}
},
strictUnits: {
src: [`${lessFolder}/units/strict/*.less`],
src: [`${testsConfigFolder}/units/strict/*.less`],
options: {

@@ -50,4 +46,4 @@ helpers: 'test/browser/runner-strict-units-options.js',

src: [
`${lessFolder}/errors/*.less`,
`${testFolder}/errors/javascript-error.less`,
`${testFolder}/tests-error/eval/*.less`,
`${testFolder}/tests-error/parse/*.less`,
`${localTests}/less/errors/*.less`

@@ -63,3 +59,3 @@ ],

noJsErrors: {
src: [`${lessFolder}/no-js-errors/*.less`],
src: [`${testsConfigFolder}/no-js-errors/*.less`],
options: {

@@ -149,3 +145,3 @@ helpers: 'test/browser/runner-no-js-errors-options.js',

postProcessorPlugin: {
src: [`${lessFolder}/postProcessorPlugin/*.less`],
src: [`${testsConfigFolder}/postProcessorPlugin/*.less`],
options: {

@@ -162,3 +158,3 @@ helpers: [

preProcessorPlugin: {
src: [`${lessFolder}/preProcessorPlugin/*.less`],
src: [`${testsConfigFolder}/preProcessorPlugin/*.less`],
options: {

@@ -174,3 +170,3 @@ helpers: [

visitorPlugin: {
src: [`${lessFolder}/visitorPlugin/*.less`],
src: [`${testsConfigFolder}/visitorPlugin/*.less`],
options: {

@@ -186,3 +182,3 @@ helpers: [

filemanagerPlugin: {
src: [`${lessFolder}/filemanagerPlugin/*.less`],
src: [`${testsConfigFolder}/filemanagerPlugin/*.less`],
options: {

@@ -189,0 +185,0 @@ helpers: [

@@ -28,5 +28,16 @@ const html = require('html-template-tag')

var pathParts = fullLessName.split('/');
var fullCssName = fullLessName
.replace(/\/(browser|test-data)\/less\//g, '/$1/css/')
.replace(/less$/, 'css')
var fullCssName = fullLessName.replace(/less$/, 'css');
// Check if the CSS file exists in the same directory as the LESS file
var fs = require('fs');
var cssExists = fs.existsSync(fullCssName);
// If not, try the css/ directory for local browser tests
if (!cssExists && fullLessName.includes('/test/browser/less/')) {
var cssInCssDir = fullLessName.replace('/test/browser/less/', '/test/browser/css/').replace(/less$/, 'css');
if (fs.existsSync(cssInCssDir)) {
fullCssName = cssInCssDir;
}
}
var lessName = pathParts[pathParts.length - 1];

@@ -33,0 +44,0 @@ var name = lessName.split('.')[0];

@@ -9,3 +9,3 @@ var less = {

// test inline less in style tags by grabbing an assortment of less files and doing `@import`s
var testFiles = ['charsets', 'colors', 'comments', 'css-3', 'strings', 'media', 'mixins'],
var testFiles = ['charsets/charsets', 'color-functions/basic', 'comments/comments', 'css-3/css-3', 'strings/strings', 'media/media', 'mixins/mixins'],
testSheets = [];

@@ -18,9 +18,9 @@

*/
var lessFolder = '../../node_modules/@less/test-data/less'
var cssFolder = '../../node_modules/@less/test-data/css'
var lessFolder = '../../node_modules/@less/test-data/tests-unit'
var cssFolder = '../../node_modules/@less/test-data/tests-unit'
for (var i = 0; i < testFiles.length; i++) {
var file = testFiles[i],
lessPath = lessFolder + '/_main/' + file + '.less',
cssPath = cssFolder + '/_main/' + file + '.css',
lessPath = lessFolder + '/' + file + '.less',
cssPath = cssFolder + '/' + file + '.css',
lessStyle = document.createElement('style'),

@@ -27,0 +27,0 @@ cssLink = document.createElement('link'),

@@ -1,92 +0,293 @@

var lessTest = require('./less-test'),
lessTester = lessTest(),
path = require('path'),
stylize = require('../lib/less-node/lessc-helper').stylize,
nock = require('nock');
// Mock needle for HTTP requests BEFORE any other requires
const Module = require('module');
const originalRequire = Module.prototype.require;
Module.prototype.require = function(id) {
if (id === 'needle') {
return {
get: function(url, options, callback) {
// Handle CDN requests
if (url.includes('cdn.jsdelivr.net')) {
if (url.includes('selectors.less')) {
setTimeout(() => {
callback(null, { statusCode: 200 }, fs.readFileSync(path.join(__dirname, '../../test-data/tests-unit/selectors/selectors.less'), 'utf8'));
}, 10);
return;
}
if (url.includes('media.less')) {
setTimeout(() => {
callback(null, { statusCode: 200 }, fs.readFileSync(path.join(__dirname, '../../test-data/tests-unit/media/media.less'), 'utf8'));
}, 10);
return;
}
if (url.includes('empty.less')) {
setTimeout(() => {
callback(null, { statusCode: 200 }, fs.readFileSync(path.join(__dirname, '../../test-data/tests-unit/empty/empty.less'), 'utf8'));
}, 10);
return;
}
}
// Handle redirect test - simulate needle's automatic redirect handling
if (url.includes('example.com/redirect.less')) {
setTimeout(() => {
// Simulate the final response after needle automatically follows the redirect
callback(null, { statusCode: 200 }, 'h1 { color: blue; }');
}, 10);
return;
}
if (url.includes('example.com/target.less')) {
setTimeout(() => {
callback(null, { statusCode: 200 }, 'h1 { color: blue; }');
}, 10);
return;
}
// Default error for unmocked URLs
setTimeout(() => {
callback(new Error('Unmocked URL: ' + url), null, null);
}, 10);
}
};
}
return originalRequire.apply(this, arguments);
};
// Now load other modules after mocking is set up
var path = require('path'),
fs = require('fs'),
lessTest = require('./less-test'),
stylize = require('../lib/less-node/lessc-helper').stylize;
// Parse command line arguments for test filtering
var args = process.argv.slice(2);
var testFilter = args.length > 0 ? args[0] : null;
// Create the test runner with the filter
var lessTester = lessTest(testFilter);
// HTTP mocking is now handled by needle mocking above
// Test HTTP redirect functionality
function testHttpRedirects() {
const less = require('../lib/less-node').default;
console.log('🧪 Testing HTTP redirect functionality...');
const redirectTest = `
@import "https://example.com/redirect.less";
h1 { color: red; }
`;
return less.render(redirectTest, {
filename: 'test-redirect.less'
}).then(result => {
console.log('✅ HTTP redirect test SUCCESS:');
console.log(result.css);
// Check if both imported and local content are present
if (result.css.includes('color: blue') && result.css.includes('color: red')) {
console.log('🎉 HTTP redirect test PASSED - both imported and local content found');
return true;
} else {
console.log('❌ HTTP redirect test FAILED - missing expected content');
return false;
}
}).catch(err => {
console.log('❌ HTTP redirect test ERROR:');
console.log(err.message);
return false;
});
}
// Test import-remote functionality
function testImportRemote() {
const less = require('../lib/less-node').default;
const fs = require('fs');
const path = require('path');
console.log('🧪 Testing import-remote functionality...');
const testFile = path.join(__dirname, '../../test-data/tests-unit/import/import-remote.less');
const expectedFile = path.join(__dirname, '../../test-data/tests-unit/import/import-remote.css');
const content = fs.readFileSync(testFile, 'utf8');
const expected = fs.readFileSync(expectedFile, 'utf8');
return less.render(content, {
filename: testFile
}).then(result => {
console.log('✅ Import-remote test SUCCESS:');
console.log('Expected:', expected.trim());
console.log('Actual:', result.css.trim());
if (result.css.trim() === expected.trim()) {
console.log('🎉 Import-remote test PASSED - CDN imports and variable resolution working');
return true;
} else {
console.log('❌ Import-remote test FAILED - output mismatch');
return false;
}
}).catch(err => {
console.log('❌ Import-remote test ERROR:');
console.log(err.message);
return false;
});
}
console.log('\n' + stylize('Less', 'underline') + '\n');
if (testFilter) {
console.log('Running tests matching: ' + testFilter + '\n');
}
// Glob patterns for main test runs (excluding problematic tests that will run separately)
var globPatterns = [
'tests-config/*/*.less',
'tests-unit/*/*.less',
// Debug tests have nested subdirectories (comments/, mediaquery/, all/)
'tests-config/debug/*/linenumbers-*.less',
'!tests-config/sourcemaps/**/*.less', // Exclude sourcemaps (need special handling)
'!tests-config/sourcemaps-empty/*', // Exclude sourcemaps-empty (need special handling)
'!tests-config/sourcemaps-disable-annotation/*', // Exclude sourcemaps-disable-annotation (need special handling)
'!tests-config/sourcemaps-variable-selector/*', // Exclude sourcemaps-variable-selector (need special handling)
'!tests-config/globalVars/*', // Exclude globalVars (need JSON config handling)
'!tests-config/modifyVars/*', // Exclude modifyVars (need JSON config handling)
'!tests-config/js-type-errors/*', // Exclude js-type-errors (need special test function)
'!tests-config/no-js-errors/*', // Exclude no-js-errors (need special test function)
'!tests-unit/import/import-remote.less', // Exclude import-remote (tested separately in isolation)
// HTTP import tests are now included since we have needle mocking
];
var testMap = [
[{
// TODO: Change this to rewriteUrls: 'all' once the relativeUrls option is removed
relativeUrls: true,
silent: true,
javascriptEnabled: true
}, '_main/'],
[{}, 'namespacing/'],
[{
math: 'parens'
}, 'math/strict/'],
[{
math: 'parens-division'
}, 'math/parens-division/'],
[{
math: 'always'
}, 'math/always/'],
// Use legacy strictMath: true here to demonstrate it still works
[{strictMath: true, strictUnits: true, javascriptEnabled: true}, '../errors/eval/',
lessTester.testErrors, null],
[{strictMath: true, strictUnits: true, javascriptEnabled: true}, '../errors/parse/',
lessTester.testErrors, null],
[{math: 'strict', strictUnits: true, javascriptEnabled: true}, 'js-type-errors/',
lessTester.testTypeErrors, null],
[{math: 'strict', strictUnits: true, javascriptEnabled: false}, 'no-js-errors/',
lessTester.testErrors, null],
[{math: 'strict', dumpLineNumbers: 'comments'}, 'debug/', null,
function(name) { return name + '-comments'; }],
[{math: 'strict', dumpLineNumbers: 'mediaquery'}, 'debug/', null,
function(name) { return name + '-mediaquery'; }],
[{math: 'strict', dumpLineNumbers: 'all'}, 'debug/', null,
function(name) { return name + '-all'; }],
// TODO: Change this to rewriteUrls: false once the relativeUrls option is removed
[{math: 'strict', relativeUrls: false, rootpath: 'folder (1)/'}, 'static-urls/'],
[{math: 'strict', compress: true}, 'compression/'],
// Main test runs using glob patterns (cosmiconfig handles configs)
{
patterns: globPatterns
},
[{math: 0, strictUnits: true}, 'units/strict/'],
[{math: 0, strictUnits: false}, 'units/no-strict/'],
// Error tests
{
patterns: ['tests-error/eval/*.less'],
verifyFunction: lessTester.testErrors
},
{
patterns: ['tests-error/parse/*.less'],
verifyFunction: lessTester.testErrors
},
[{math: 'strict', strictUnits: true, sourceMap: true, globalVars: true }, 'sourcemaps/',
lessTester.testSourcemap, null, null,
function(filename, type, baseFolder) {
// Special test cases with specific handling
{
patterns: ['tests-config/js-type-errors/*.less'],
verifyFunction: lessTester.testTypeErrors
},
{
patterns: ['tests-config/no-js-errors/*.less'],
verifyFunction: lessTester.testErrors
},
// Sourcemap tests with special handling
{
patterns: [
'tests-config/sourcemaps/**/*.less',
'tests-config/sourcemaps-url/**/*.less',
'tests-config/sourcemaps-rootpath/**/*.less',
'tests-config/sourcemaps-basepath/**/*.less',
'tests-config/sourcemaps-include-source/**/*.less'
],
verifyFunction: lessTester.testSourcemap,
getFilename: function(filename, type, baseFolder) {
if (type === 'vars') {
return path.join(baseFolder, filename) + '.json';
}
return path.join('test/sourcemaps', filename) + '.json';
}],
// Extract just the filename (without directory) for the JSON file
var jsonFilename = path.basename(filename);
// For sourcemap type, return path relative to test directory
if (type === 'sourcemap') {
return path.join('test/sourcemaps', jsonFilename) + '.json';
}
return path.join('test/sourcemaps', jsonFilename) + '.json';
}
},
{
patterns: ['tests-config/sourcemaps-empty/*.less'],
verifyFunction: lessTester.testEmptySourcemap
},
{
patterns: ['tests-config/sourcemaps-disable-annotation/*.less'],
verifyFunction: lessTester.testSourcemapWithoutUrlAnnotation
},
{
patterns: ['tests-config/sourcemaps-variable-selector/*.less'],
verifyFunction: lessTester.testSourcemapWithVariableInSelector
},
[{math: 'strict', strictUnits: true, globalVars: true }, '_main/import/json/',
lessTester.testImports, null, true,
function(filename, type, baseFolder) {
return path.join(baseFolder, filename) + '.json';
}],
[{math: 'strict', strictUnits: true, sourceMap: {sourceMapFileInline: true}},
'sourcemaps-empty/', lessTester.testEmptySourcemap],
[{math: 'strict', strictUnits: true, sourceMap: {disableSourcemapAnnotation: true}},
'sourcemaps-disable-annotation/', lessTester.testSourcemapWithoutUrlAnnotation],
[{math: 'strict', strictUnits: true, sourceMap: true},
'sourcemaps-variable-selector/', lessTester.testSourcemapWithVariableInSelector],
[{globalVars: true, banner: '/**\n * Test\n */\n'}, 'globalVars/',
null, null, null, function(name, type, baseFolder) { return path.join(baseFolder, name) + '.json'; }],
[{modifyVars: true}, 'modifyVars/',
null, null, null, function(name, type, baseFolder) { return path.join(baseFolder, name) + '.json'; }],
[{urlArgs: '424242'}, 'url-args/'],
[{rewriteUrls: 'all'}, 'rewrite-urls-all/'],
[{rewriteUrls: 'local'}, 'rewrite-urls-local/'],
[{rootpath: 'http://example.com/assets/css/', rewriteUrls: 'all'}, 'rootpath-rewrite-urls-all/'],
[{rootpath: 'http://example.com/assets/css/', rewriteUrls: 'local'}, 'rootpath-rewrite-urls-local/'],
[{paths: ['data/', '_main/import/']}, 'include-path/'],
[{paths: 'data/'}, 'include-path-string/'],
[{plugin: 'test/plugins/postprocess/'}, 'postProcessorPlugin/'],
[{plugin: 'test/plugins/preprocess/'}, 'preProcessorPlugin/'],
[{plugin: 'test/plugins/visitor/'}, 'visitorPlugin/'],
[{plugin: 'test/plugins/filemanager/'}, 'filemanagerPlugin/'],
[{math: 0}, '3rd-party/'],
[{ processImports: false }, 'process-imports/']
// Import tests with JSON configs
{
patterns: ['tests-config/globalVars/*.less'],
lessOptions: {
globalVars: function(file) {
const fs = require('fs');
const path = require('path');
const basename = path.basename(file, '.less');
const jsonPath = path.join(path.dirname(file), basename + '.json');
try {
return JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
} catch (e) {
return {};
}
}
}
},
{
patterns: ['tests-config/modifyVars/*.less'],
lessOptions: {
modifyVars: function(file) {
const fs = require('fs');
const path = require('path');
const basename = path.basename(file, '.less');
const jsonPath = path.join(path.dirname(file), basename + '.json');
try {
return JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
} catch (e) {
return {};
}
}
}
}
];
testMap.forEach(function(args) {
lessTester.runTestSet.apply(lessTester, args)
// Note: needle mocking is set up globally at the top of the file
testMap.forEach(function(testConfig) {
// For glob patterns, pass lessOptions as the first parameter and patterns as the second
if (testConfig.patterns) {
lessTester.runTestSet(
testConfig.lessOptions || {}, // First param: options (including lessOptions)
testConfig.patterns, // Second param: patterns
testConfig.verifyFunction || null, // Third param: verifyFunction
testConfig.nameModifier || null, // Fourth param: nameModifier
testConfig.doReplacements || null, // Fifth param: doReplacements
testConfig.getFilename || null // Sixth param: getFilename
);
} else {
// Legacy format for non-glob tests
var args = [
testConfig.options || {}, // First param: options
testConfig.foldername, // Second param: foldername
testConfig.verifyFunction || null, // Third param: verifyFunction
testConfig.nameModifier || null, // Fourth param: nameModifier
testConfig.doReplacements || null, // Fifth param: doReplacements
testConfig.getFilename || null // Sixth param: getFilename
];
lessTester.runTestSet.apply(lessTester, args);
}
});
lessTester.testSyncronous({syncImport: true}, '_main/import');
lessTester.testSyncronous({syncImport: true}, '_main/plugin');
lessTester.testSyncronous({syncImport: true}, 'math/strict/css');
// Special synchronous tests
lessTester.testSyncronous({syncImport: true}, 'tests-unit/import/import');
lessTester.testSyncronous({syncImport: true}, 'tests-config/math-strict/css');
lessTester.testNoOptions();

@@ -97,17 +298,11 @@ lessTester.testDisablePluginRule();

(() => {
// Create new tester, since tests are not independent and tests
// above modify tester in a way that breaks remote imports.
lessTester = lessTest();
var scope = nock('https://example.com')
.get('/redirect.less').query(true)
.reply(301, null, { location: '/target.less' })
.get('/target.less').query(true)
.reply(200);
lessTester.runTestSet(
{},
'import-redirect/',
lessTester.testImportRedirect(scope)
);
lessTester.finished();
})();
// Test HTTP redirect functionality
console.log('\nTesting HTTP redirect functionality...');
testHttpRedirects();
console.log('HTTP redirect test completed');
// Test import-remote functionality in isolation
console.log('\nTesting import-remote functionality...');
testImportRemote();
console.log('Import-remote test completed');
/* jshint latedef: nofunc */
var semver = require('semver');
var logger = require('../lib/less/logger').default;
var { cosmiconfigSync } = require('cosmiconfig');
var glob = require('glob');

@@ -21,3 +23,3 @@ var isVerbose = process.env.npm_config_loglevel !== 'concise';

module.exports = function() {
module.exports = function(testFilter) {
var path = require('path'),

@@ -33,7 +35,7 @@ fs = require('fs'),

var oneTestOnly = process.argv[2],
var oneTestOnly = testFilter || process.argv[2],
isFinished = false;
var testFolder = path.dirname(require.resolve('@less/test-data'));
var lessFolder = path.join(testFolder, 'less');
var lessFolder = testFolder;

@@ -88,3 +90,89 @@ // Define String.prototype.endsWith if it doesn't exist (in older versions of node)

function testSourcemap(name, err, compiledLess, doReplacements, sourcemap, baseFolder) {
function validateSourcemapMappings(sourcemap, lessFile, compiledCSS) {
// Validate sourcemap mappings using SourceMapConsumer
var SourceMapConsumer = require('source-map').SourceMapConsumer;
// sourcemap can be either a string or already parsed object
var sourceMapObj = typeof sourcemap === 'string' ? JSON.parse(sourcemap) : sourcemap;
var consumer = new SourceMapConsumer(sourceMapObj);
// Read the LESS source file
var lessSource = fs.readFileSync(lessFile, 'utf8');
var lessLines = lessSource.split('\n');
// Use the compiled CSS (remove sourcemap annotation for validation)
var cssSource = compiledCSS.replace(/\/\*# sourceMappingURL=.*\*\/\s*$/, '').trim();
var cssLines = cssSource.split('\n');
var errors = [];
var validatedMappings = 0;
// Validate mappings for each line in the CSS
for (var cssLine = 1; cssLine <= cssLines.length; cssLine++) {
var cssLineContent = cssLines[cssLine - 1];
// Skip empty lines
if (!cssLineContent.trim()) {
continue;
}
// Check mapping for the start of this CSS line
var mapping = consumer.originalPositionFor({
line: cssLine,
column: 0
});
if (mapping.source) {
validatedMappings++;
// Verify the source file exists in the sourcemap
if (!sourceMapObj.sources || sourceMapObj.sources.indexOf(mapping.source) === -1) {
errors.push('Line ' + cssLine + ': mapped to source "' + mapping.source + '" which is not in sources array');
}
// Verify the line number is valid
if (mapping.line && mapping.line > 0) {
// If we can find the source file, validate the line exists
var sourceIndex = sourceMapObj.sources.indexOf(mapping.source);
if (sourceIndex >= 0 && sourceMapObj.sourcesContent && sourceMapObj.sourcesContent[sourceIndex] !== undefined && sourceMapObj.sourcesContent[sourceIndex] !== null) {
var sourceContent = sourceMapObj.sourcesContent[sourceIndex];
// Ensure sourceContent is a string (it should be, but be defensive)
if (typeof sourceContent !== 'string') {
sourceContent = String(sourceContent);
}
// Split by newline - handle both \n and \r\n
var sourceLines = sourceContent.split(/\r?\n/);
if (mapping.line > sourceLines.length) {
errors.push('Line ' + cssLine + ': mapped to line ' + mapping.line + ' in "' + mapping.source + '" but source only has ' + sourceLines.length + ' lines');
}
} else if (sourceIndex >= 0) {
// Source content not embedded, try to validate against the actual file if it matches
// This is a best-effort validation
}
}
}
}
// Validate that all sources in the sourcemap are valid
if (sourceMapObj.sources) {
sourceMapObj.sources.forEach(function(source, index) {
if (sourceMapObj.sourcesContent && sourceMapObj.sourcesContent[index]) {
// Source content is embedded, validate it's not empty
if (!sourceMapObj.sourcesContent[index].trim()) {
errors.push('Source "' + source + '" has empty content');
}
}
});
}
if (consumer.destroy && typeof consumer.destroy === 'function') {
consumer.destroy();
}
return {
valid: errors.length === 0,
errors: errors,
mappingsValidated: validatedMappings
};
}
function testSourcemap(name, err, compiledLess, doReplacements, sourcemap, baseFolder, getFilename) {
if (err) {

@@ -95,23 +183,106 @@ fail('ERROR: ' + (err && err.message));

// Check the sourceMappingURL at the bottom of the file
var expectedSourceMapURL = name + '.css.map',
sourceMappingPrefix = '/*# sourceMappingURL=',
sourceMappingSuffix = ' */',
expectedCSSAppendage = sourceMappingPrefix + expectedSourceMapURL + sourceMappingSuffix;
if (!compiledLess.endsWith(expectedCSSAppendage)) {
// To display a better error message, we need to figure out what the actual sourceMappingURL value was, if it was even present
var indexOfSourceMappingPrefix = compiledLess.indexOf(sourceMappingPrefix);
if (indexOfSourceMappingPrefix === -1) {
fail('ERROR: sourceMappingURL was not found in ' + baseFolder + '/' + name + '.css.');
return;
}
// Default expected URL is name + '.css.map', but can be overridden by sourceMapURL option
var sourceMappingPrefix = '/*# sourceMappingURL=',
sourceMappingSuffix = ' */';
var indexOfSourceMappingPrefix = compiledLess.indexOf(sourceMappingPrefix);
if (indexOfSourceMappingPrefix === -1) {
fail('ERROR: sourceMappingURL was not found in ' + baseFolder + '/' + name + '.css.');
return;
}
var startOfSourceMappingValue = indexOfSourceMappingPrefix + sourceMappingPrefix.length,
indexOfSuffix = compiledLess.indexOf(sourceMappingSuffix, startOfSourceMappingValue),
actualSourceMapURL = compiledLess.substring(startOfSourceMappingValue, indexOfSuffix === -1 ? compiledLess.length : indexOfSuffix).trim();
// For tests with custom sourceMapURL, we just verify it exists and is non-empty
// The actual value will be validated by comparing the sourcemap JSON
if (!actualSourceMapURL) {
fail('ERROR: sourceMappingURL is empty in ' + baseFolder + '/' + name + '.css.');
return;
}
var startOfSourceMappingValue = indexOfSourceMappingPrefix + sourceMappingPrefix.length,
indexOfNextSpace = compiledLess.indexOf(' ', startOfSourceMappingValue),
actualSourceMapURL = compiledLess.substring(startOfSourceMappingValue, indexOfNextSpace === -1 ? compiledLess.length : indexOfNextSpace);
fail('ERROR: sourceMappingURL should be "' + expectedSourceMapURL + '" but is "' + actualSourceMapURL + '".');
// Use getFilename if available (for sourcemap tests with subdirectories)
var jsonPath;
if (getFilename && typeof getFilename === 'function') {
jsonPath = getFilename(name, 'sourcemap', baseFolder);
} else {
// Fallback: extract just the filename for sourcemap JSON files
var jsonFilename = path.basename(name);
jsonPath = path.join('test/sourcemaps', jsonFilename) + '.json';
}
fs.readFile(path.join('test/', name) + '.json', 'utf8', function (e, expectedSourcemap) {
fs.readFile(jsonPath, 'utf8', function (e, expectedSourcemap) {
process.stdout.write('- ' + path.join(baseFolder, name) + ': ');
if (sourcemap === expectedSourcemap) {
if (e) {
fail('ERROR: Could not read expected sourcemap file: ' + jsonPath + ' - ' + e.message);
return;
}
// Apply doReplacements to the expected sourcemap to handle {path} placeholders
// This normalizes absolute paths that differ between environments
// For sourcemaps, we need to ensure {path} uses forward slashes to avoid breaking JSON
// (backslashes in JSON strings need escaping, and sourcemaps should use forward slashes anyway)
var replacementPath = path.join(path.dirname(path.join(baseFolder, name) + '.less'), '/');
// Normalize to forward slashes for sourcemap JSON (web-compatible)
replacementPath = replacementPath.replace(/\\/g, '/');
// Replace {path} with normalized forward-slash path BEFORE calling doReplacements
// This ensures the JSON is always valid and uses web-compatible paths
expectedSourcemap = expectedSourcemap.replace(/\{path\}/g, replacementPath);
// Also handle other placeholders that might be in the sourcemap (but {path} is already done)
expectedSourcemap = doReplacements(expectedSourcemap, baseFolder, path.join(baseFolder, name) + '.less');
// Normalize paths in sourcemap JSON to use forward slashes (web-compatible)
// We need to parse the JSON, normalize the file property, then stringify for comparison
// This avoids breaking escape sequences like \n in the JSON string
function normalizeSourcemapPaths(sm) {
try {
var parsed = typeof sm === 'string' ? JSON.parse(sm) : sm;
if (parsed.file) {
parsed.file = parsed.file.replace(/\\/g, '/');
}
// Also normalize paths in sources array
if (parsed.sources && Array.isArray(parsed.sources)) {
parsed.sources = parsed.sources.map(function(src) {
return src.replace(/\\/g, '/');
});
}
return JSON.stringify(parsed, null, 0);
} catch (parseErr) {
// If parsing fails, return original (shouldn't happen)
return sm;
}
}
var normalizedSourcemap = normalizeSourcemapPaths(sourcemap);
var normalizedExpected = normalizeSourcemapPaths(expectedSourcemap);
if (normalizedSourcemap === normalizedExpected) {
// Validate the sourcemap mappings are correct
// Find the actual LESS file - it might be in a subdirectory
var nameParts = name.split('/');
var lessFileName = nameParts[nameParts.length - 1];
var lessFileDir = nameParts.length > 1 ? nameParts.slice(0, -1).join('/') : '';
var lessFile = path.join(lessFolder, lessFileDir, lessFileName) + '.less';
// Only validate if the LESS file exists
if (fs.existsSync(lessFile)) {
try {
// Parse the sourcemap once for validation (avoid re-parsing)
// Use the original sourcemap string, not the normalized one
var sourceMapObjForValidation = typeof sourcemap === 'string' ? JSON.parse(sourcemap) : sourcemap;
var validation = validateSourcemapMappings(sourceMapObjForValidation, lessFile, compiledLess);
if (!validation.valid) {
fail('ERROR: Sourcemap validation failed:\n' + validation.errors.join('\n'));
return;
}
if (isVerbose && validation.mappingsValidated > 0) {
process.stdout.write(' (validated ' + validation.mappingsValidated + ' mappings)');
}
} catch (validationErr) {
if (isVerbose) {
process.stdout.write(' (validation error: ' + validationErr.message + ')');
}
// Don't fail the test if validation has an error, just log it
}
}
ok('OK');

@@ -125,3 +296,3 @@ } else if (err) {

} else {
difference('FAIL', expectedSourcemap, sourcemap);
difference('FAIL', normalizedExpected, normalizedSourcemap);
}

@@ -289,3 +460,3 @@ });

var expected = '@charset "utf-8";\n';
toCSS({}, path.join(lessFolder, 'root-registry', 'root.less'), function(error, output) {
toCSS({}, path.join(lessFolder, 'tests-config', 'root-registry', 'root.less'), function(error, output) {
if (error) {

@@ -303,5 +474,38 @@ return fail('ERROR: ' + error);

var path = require('path');
var p = filename ? path.join(path.dirname(filename), '/') : directory,
pathimport = path.join(directory + 'import/'),
pathesc = p.replace(/[.:/\\]/g, function(a) { return '\\' + (a == '\\' ? '\/' : a); }),
var p = filename ? path.join(path.dirname(filename), '/') : directory;
// For debug tests in subdirectories (comments/, mediaquery/, all/),
// the import/ directory and main linenumbers.less file are at the parent debug/ level, not in the subdirectory
var isDebugSubdirectory = false;
var debugParentPath = null;
if (directory) {
// Normalize directory path separators for matching
var normalizedDir = directory.replace(/\\/g, '/');
// Check if we're in a debug subdirectory
if (normalizedDir.includes('/debug/') && (normalizedDir.includes('/comments/') || normalizedDir.includes('/mediaquery/') || normalizedDir.includes('/all/'))) {
isDebugSubdirectory = true;
// Extract the debug/ directory path (parent of the subdirectory)
// Match everything up to and including /debug/ (works with both absolute and relative paths)
var debugMatch = normalizedDir.match(/(.+\/debug)\//);
if (debugMatch) {
debugParentPath = debugMatch[1];
}
}
}
if (isDebugSubdirectory && debugParentPath) {
// For {path} placeholder, use the parent debug/ directory
// Convert back to native path format
p = debugParentPath.replace(/\//g, path.sep) + path.sep;
}
var pathimport;
if (isDebugSubdirectory && debugParentPath) {
pathimport = path.join(debugParentPath.replace(/\//g, path.sep), 'import') + path.sep;
} else {
pathimport = path.join(directory + 'import/');
}
var pathesc = p.replace(/[.:/\\]/g, function(a) { return '\\' + (a == '\\' ? '\/' : a); }),
pathimportesc = pathimport.replace(/[.:/\\]/g, function(a) { return '\\' + (a == '\\' ? '\/' : a); });

@@ -350,3 +554,14 @@

function runTestSet(options, foldername, verifyFunction, nameModifier, doReplacements, getFilename) {
options = options ? clone(options) : {};
// Handle case where first parameter is glob patterns (no options object)
if (Array.isArray(options)) {
// First parameter is glob patterns, no options object
foldername = options;
options = {};
} else if (typeof options === 'string') {
// First parameter is foldername (no options object)
foldername = options;
options = {};
} else {
options = options ? clone(options) : {};
}
runTestSetInternal(lessFolder, options, foldername, verifyFunction, nameModifier, doReplacements, getFilename);

@@ -368,16 +583,96 @@ }

function getBasename(file) {
return foldername + path.basename(file, '.less');
// Handle glob patterns with exclusions
if (Array.isArray(foldername)) {
var patterns = foldername;
var includePatterns = [];
var excludePatterns = [];
patterns.forEach(function(pattern) {
if (pattern.startsWith('!')) {
excludePatterns.push(pattern.substring(1));
} else {
includePatterns.push(pattern);
}
});
// Use glob to find all matching files, excluding the excluded patterns
var allFiles = [];
includePatterns.forEach(function(pattern) {
var files = glob.sync(pattern, {
cwd: baseFolder,
absolute: true,
ignore: excludePatterns
});
allFiles = allFiles.concat(files);
});
// Note: needle mocking is set up globally in index.js
// Process each .less file found
allFiles.forEach(function(filePath) {
if (/\.less$/.test(filePath)) {
var file = path.basename(filePath);
// For glob patterns, we need to construct the relative path differently
// The filePath is absolute, so we need to get the path relative to the test-data directory
var relativePath = path.relative(baseFolder, path.dirname(filePath)) + '/';
// Only process files that have corresponding .css files (these are the actual tests)
var cssPath = path.join(path.dirname(filePath), path.basename(file, '.less') + '.css');
if (fs.existsSync(cssPath)) {
// Process this file using the existing logic
processFileWithInfo({
file: file,
fullPath: filePath,
relativePath: relativePath
});
}
}
});
return;
}
fs.readdirSync(path.join(baseFolder, foldername)).forEach(function (file) {
if (!/\.less$/.test(file)) { return; }
function processFileWithInfo(fileInfo) {
var file = fileInfo.file;
var fullPath = fileInfo.fullPath;
var relativePath = fileInfo.relativePath;
// Load config for this specific file using cosmiconfig
var configResult = cosmiconfigSync('styles').search(path.dirname(fullPath));
// Deep clone the original options to prevent Less from modifying shared objects
var options = JSON.parse(JSON.stringify(originalOptions || {}));
if (configResult && configResult.config && configResult.config.language && configResult.config.language.less) {
// Deep clone and merge the language.less settings with the original options
var lessConfig = JSON.parse(JSON.stringify(configResult.config.language.less));
Object.keys(lessConfig).forEach(function(key) {
options[key] = lessConfig[key];
});
}
// Merge any lessOptions from the testMap (for dynamic options like getVars functions)
if (originalOptions && originalOptions.lessOptions) {
Object.keys(originalOptions.lessOptions).forEach(function(key) {
var value = originalOptions.lessOptions[key];
if (typeof value === 'function') {
// For functions, call them with the file path
var result = value(fullPath);
options[key] = result;
} else {
// For static values, use them directly
options[key] = value;
}
});
}
var options = clone(originalOptions);
// Don't pass stylize to less.render as it's not a valid option
options.stylize = stylize;
var name = getBasename(file, relativePath);
var name = getBasename(file);
if (oneTestOnly && name !== oneTestOnly) {
if (oneTestOnly && typeof oneTestOnly === 'string' && !name.includes(oneTestOnly)) {
return;

@@ -389,12 +684,16 @@ }

if (options.sourceMap && !options.sourceMap.sourceMapFileInline) {
options.sourceMap = {
sourceMapOutputFilename: name + '.css',
sourceMapBasepath: baseFolder,
sourceMapRootpath: 'testweb/',
disableSourcemapAnnotation: options.sourceMap.disableSourcemapAnnotation
};
// This options is normally set by the bin/lessc script. Setting it causes the sourceMappingURL comment to be appended to the CSS
// output. The value is designed to allow the sourceMapBasepath option to be tested, as it should be removed by less before
// setting the sourceMappingURL value, leaving just the sourceMapOutputFilename and .map extension.
options.sourceMap.sourceMapFilename = options.sourceMap.sourceMapBasepath + '/' + options.sourceMap.sourceMapOutputFilename + '.map';
// Set test infrastructure defaults only if not already set by styles.config.cjs
// Less.js core (parse-tree.js) will handle normalization of:
// - sourceMapBasepath (defaults to input file's directory)
// - sourceMapInputFilename (defaults to options.filename)
// - sourceMapFilename (derived from sourceMapOutputFilename or input filename)
// - sourceMapOutputFilename (derived from input filename if not set)
if (!options.sourceMap.sourceMapOutputFilename) {
// Needed for sourcemap file name in JSON output
options.sourceMap.sourceMapOutputFilename = name + '.css';
}
if (!options.sourceMap.sourceMapRootpath) {
// Test-specific default for consistent test output paths
options.sourceMap.sourceMapRootpath = 'testweb/';
}
}

@@ -404,3 +703,3 @@

try {
return JSON.parse(fs.readFileSync(getFilename(getBasename(file), 'vars', baseFolder), 'utf8'));
return JSON.parse(fs.readFileSync(getFilename(getBasename(file, relativePath), 'vars', baseFolder), 'utf8'));
}

@@ -414,3 +713,3 @@ catch (e) {

queue(function() {
toCSS(options, path.join(baseFolder, foldername + file), function (err, result) {
toCSS(options, fullPath, function (err, result) {

@@ -431,3 +730,3 @@ if (doubleCallCheck) {

var verificationResult = verifyFunction(
name, err, result && result.css, doReplacements, result && result.map, baseFolder, result && result.imports
name, err, result && result.css, doReplacements, result && result.map, baseFolder, result && result.imports, getFilename
);

@@ -455,14 +754,65 @@ release();

fs.readFile(path.join(testFolder, 'css', css_name) + '.css', 'utf8', function (e, css) {
process.stdout.write('- ' + path.join(baseFolder, css_name) + ': ');
// Check if we're using the new co-located structure (tests-unit/ or tests-config/) or the old separated structure
var cssPath;
if (relativePath.startsWith('tests-unit/') || relativePath.startsWith('tests-config/')) {
// New co-located structure: CSS file is in the same directory as LESS file
cssPath = path.join(path.dirname(fullPath), path.basename(file, '.less') + '.css');
} else {
// Old separated structure: CSS file is in separate css/ folder
// Windows compatibility: css_name may already contain path separators
// Use path.join with empty string to let path.join handle normalization
cssPath = path.join(testFolder, css_name) + '.css';
}
css = css && doReplacements(css, path.join(baseFolder, foldername));
if (result.css === css) { ok('OK'); }
else {
difference('FAIL', css, result.css);
// For the new structure, we need to handle replacements differently
var replacementPath;
if (relativePath.startsWith('tests-unit/') || relativePath.startsWith('tests-config/')) {
replacementPath = path.dirname(fullPath);
// Ensure replacementPath ends with a path separator for consistent matching
if (!replacementPath.endsWith(path.sep)) {
replacementPath += path.sep;
}
release();
});
} else {
replacementPath = path.join(baseFolder, relativePath);
}
var testName = fullPath.replace(/\.less$/, '');
process.stdout.write('- ' + testName + ': ');
var css = fs.readFileSync(cssPath, 'utf8');
css = css && doReplacements(css, replacementPath);
if (result.css === css) { ok('OK'); }
else {
difference('FAIL', css, result.css);
}
release();
});
});
}
function getBasename(file, relativePath) {
var basePath = relativePath || foldername;
// Ensure basePath ends with a slash for proper path construction
if (basePath.charAt(basePath.length - 1) !== '/') {
basePath = basePath + '/';
}
return basePath + path.basename(file, '.less');
}
// This function is only called for non-glob patterns now
// For glob patterns, we use the glob library in the calling code
var dirPath = path.join(baseFolder, foldername);
var items = fs.readdirSync(dirPath);
items.forEach(function(item) {
if (/\.less$/.test(item)) {
processFileWithInfo({
file: item,
fullPath: path.join(dirPath, item),
relativePath: foldername
});
}
});

@@ -472,11 +822,19 @@ }

function diff(left, right) {
require('diff').diffLines(left, right).forEach(function(item) {
if (item.added || item.removed) {
var text = item.value && item.value.replace('\n', String.fromCharCode(182) + '\n').replace('\ufeff', '[[BOM]]');
process.stdout.write(stylize(text, item.added ? 'green' : 'red'));
} else {
process.stdout.write(item.value && item.value.replace('\ufeff', '[[BOM]]'));
}
// Configure chalk to always show colors
var chalk = require('chalk');
chalk.level = 3; // Force colors on
// Use jest-diff for much clearer output like Vitest
var diffResult = require('jest-diff').diffStringsUnified(left || '', right || '', {
expand: false,
includeChangeCounts: true,
contextLines: 1,
aColor: chalk.red,
bColor: chalk.green,
changeColor: chalk.inverse,
commonColor: chalk.dim
});
process.stdout.write('\n');
// jest-diff returns a string with ANSI colors, so we can output it directly
process.stdout.write(diffResult + '\n');
}

@@ -494,2 +852,5 @@

// Only show the diff, not the full text
process.stdout.write(stylize('Diff:', 'yellow') + '\n');
diff(left || '', right || '');

@@ -547,23 +908,37 @@ endTest();

function toCSS(options, filePath, callback) {
options = options || {};
// Deep clone options to prevent modifying the original, but preserve functions
var originalOptions = options || {};
options = JSON.parse(JSON.stringify(originalOptions));
// Restore functions that were lost in JSON serialization
if (originalOptions.getVars) {
options.getVars = originalOptions.getVars;
}
var str = fs.readFileSync(filePath, 'utf8'), addPath = path.dirname(filePath);
// Initialize paths array if it doesn't exist
if (typeof options.paths !== 'string') {
options.paths = options.paths || [];
if (!contains(options.paths, addPath)) {
options.paths.push(addPath);
}
} else {
options.paths = [options.paths]
options.paths = [options.paths];
}
// Add the current directory to paths if not already present
if (!contains(options.paths, addPath)) {
options.paths.push(addPath);
}
// Resolve all paths relative to the test file's directory
options.paths = options.paths.map(searchPath => {
return path.resolve(lessFolder, searchPath)
if (path.isAbsolute(searchPath)) {
return searchPath;
}
// Resolve relative to the test file's directory
return path.resolve(path.dirname(filePath), searchPath);
})
options.filename = path.resolve(process.cwd(), filePath);
options.optimization = options.optimization || 0;
if (options.globalVars) {
options.globalVars = options.getVars(filePath);
} else if (options.modifyVars) {
options.modifyVars = options.getVars(filePath);
}
// Note: globalVars and modifyVars are now handled via styles.config.cjs or lessOptions
if (options.plugin) {

@@ -591,18 +966,3 @@ var Plugin = require(path.resolve(process.cwd(), options.plugin));

function testImportRedirect(nockScope) {
return (name, err, css, doReplacements, sourcemap, baseFolder) => {
process.stdout.write('- ' + path.join(baseFolder, name) + ': ');
if (err) {
fail('FAIL: ' + (err && err.message));
return;
}
const expected = 'h1 {\n color: red;\n}\n';
if (css !== expected) {
difference('FAIL', expected, css);
return;
}
nockScope.done();
ok('OK');
};
}
// HTTP redirect testing is now handled directly in test/index.js

@@ -636,3 +996,2 @@ function testDisablePluginRule() {

testImports: testImports,
testImportRedirect: testImportRedirect,
testEmptySourcemap: testEmptySourcemap,

@@ -639,0 +998,0 @@ testNoOptions: testNoOptions,

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
// Split the input into chunks.
function default_1(input, fail) {
var len = input.length;
var level = 0;
var parenLevel = 0;
var lastOpening;
var lastOpeningParen;
var lastMultiComment;
var lastMultiCommentEndBrace;
var chunks = [];
var emitFrom = 0;
var chunkerCurrentIndex;
var currentChunkStartIndex;
var cc;
var cc2;
var matched;
function emitChunk(force) {
var len = chunkerCurrentIndex - emitFrom;
if (((len < 512) && !force) || !len) {
return;
}
chunks.push(input.slice(emitFrom, chunkerCurrentIndex + 1));
emitFrom = chunkerCurrentIndex + 1;
}
for (chunkerCurrentIndex = 0; chunkerCurrentIndex < len; chunkerCurrentIndex++) {
cc = input.charCodeAt(chunkerCurrentIndex);
if (((cc >= 97) && (cc <= 122)) || (cc < 34)) {
// a-z or whitespace
continue;
}
switch (cc) {
case 40: // (
parenLevel++;
lastOpeningParen = chunkerCurrentIndex;
continue;
case 41: // )
if (--parenLevel < 0) {
return fail('missing opening `(`', chunkerCurrentIndex);
}
continue;
case 59: // ;
if (!parenLevel) {
emitChunk();
}
continue;
case 123: // {
level++;
lastOpening = chunkerCurrentIndex;
continue;
case 125: // }
if (--level < 0) {
return fail('missing opening `{`', chunkerCurrentIndex);
}
if (!level && !parenLevel) {
emitChunk();
}
continue;
case 92: // \
if (chunkerCurrentIndex < len - 1) {
chunkerCurrentIndex++;
continue;
}
return fail('unescaped `\\`', chunkerCurrentIndex);
case 34:
case 39:
case 96: // ", ' and `
matched = 0;
currentChunkStartIndex = chunkerCurrentIndex;
for (chunkerCurrentIndex = chunkerCurrentIndex + 1; chunkerCurrentIndex < len; chunkerCurrentIndex++) {
cc2 = input.charCodeAt(chunkerCurrentIndex);
if (cc2 > 96) {
continue;
}
if (cc2 == cc) {
matched = 1;
break;
}
if (cc2 == 92) { // \
if (chunkerCurrentIndex == len - 1) {
return fail('unescaped `\\`', chunkerCurrentIndex);
}
chunkerCurrentIndex++;
}
}
if (matched) {
continue;
}
return fail("unmatched `".concat(String.fromCharCode(cc), "`"), currentChunkStartIndex);
case 47: // /, check for comment
if (parenLevel || (chunkerCurrentIndex == len - 1)) {
continue;
}
cc2 = input.charCodeAt(chunkerCurrentIndex + 1);
if (cc2 == 47) {
// //, find lnfeed
for (chunkerCurrentIndex = chunkerCurrentIndex + 2; chunkerCurrentIndex < len; chunkerCurrentIndex++) {
cc2 = input.charCodeAt(chunkerCurrentIndex);
if ((cc2 <= 13) && ((cc2 == 10) || (cc2 == 13))) {
break;
}
}
}
else if (cc2 == 42) {
// /*, find */
lastMultiComment = currentChunkStartIndex = chunkerCurrentIndex;
for (chunkerCurrentIndex = chunkerCurrentIndex + 2; chunkerCurrentIndex < len - 1; chunkerCurrentIndex++) {
cc2 = input.charCodeAt(chunkerCurrentIndex);
if (cc2 == 125) {
lastMultiCommentEndBrace = chunkerCurrentIndex;
}
if (cc2 != 42) {
continue;
}
if (input.charCodeAt(chunkerCurrentIndex + 1) == 47) {
break;
}
}
if (chunkerCurrentIndex == len - 1) {
return fail('missing closing `*/`', currentChunkStartIndex);
}
chunkerCurrentIndex++;
}
continue;
case 42: // *, check for unmatched */
if ((chunkerCurrentIndex < len - 1) && (input.charCodeAt(chunkerCurrentIndex + 1) == 47)) {
return fail('unmatched `/*`', chunkerCurrentIndex);
}
continue;
}
}
if (level !== 0) {
if ((lastMultiComment > lastOpening) && (lastMultiCommentEndBrace > lastMultiComment)) {
return fail('missing closing `}` or `*/`', lastOpening);
}
else {
return fail('missing closing `}`', lastOpening);
}
}
else if (parenLevel !== 0) {
return fail('missing closing `)`', lastOpeningParen);
}
emitChunk(true);
return chunks;
}
exports.default = default_1;
//# sourceMappingURL=chunker.js.map
{"version":3,"file":"chunker.js","sourceRoot":"","sources":["../../../src/less/parser/chunker.js"],"names":[],"mappings":"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","sourcesContent":["// Split the input into chunks.\nexport default function (input, fail) {\n const len = input.length;\n let level = 0;\n let parenLevel = 0;\n let lastOpening;\n let lastOpeningParen;\n let lastMultiComment;\n let lastMultiCommentEndBrace;\n const chunks = [];\n let emitFrom = 0;\n let chunkerCurrentIndex;\n let currentChunkStartIndex;\n let cc;\n let cc2;\n let matched;\n\n function emitChunk(force) {\n const len = chunkerCurrentIndex - emitFrom;\n if (((len < 512) && !force) || !len) {\n return;\n }\n chunks.push(input.slice(emitFrom, chunkerCurrentIndex + 1));\n emitFrom = chunkerCurrentIndex + 1;\n }\n\n for (chunkerCurrentIndex = 0; chunkerCurrentIndex < len; chunkerCurrentIndex++) {\n cc = input.charCodeAt(chunkerCurrentIndex);\n if (((cc >= 97) && (cc <= 122)) || (cc < 34)) {\n // a-z or whitespace\n continue;\n }\n\n switch (cc) {\n case 40: // (\n parenLevel++;\n lastOpeningParen = chunkerCurrentIndex;\n continue;\n case 41: // )\n if (--parenLevel < 0) {\n return fail('missing opening `(`', chunkerCurrentIndex);\n }\n continue;\n case 59: // ;\n if (!parenLevel) { emitChunk(); }\n continue;\n case 123: // {\n level++;\n lastOpening = chunkerCurrentIndex;\n continue;\n case 125: // }\n if (--level < 0) {\n return fail('missing opening `{`', chunkerCurrentIndex);\n }\n if (!level && !parenLevel) { emitChunk(); }\n continue;\n case 92: // \\\n if (chunkerCurrentIndex < len - 1) { chunkerCurrentIndex++; continue; }\n return fail('unescaped `\\\\`', chunkerCurrentIndex);\n case 34:\n case 39:\n case 96: // \", ' and `\n matched = 0;\n currentChunkStartIndex = chunkerCurrentIndex;\n for (chunkerCurrentIndex = chunkerCurrentIndex + 1; chunkerCurrentIndex < len; chunkerCurrentIndex++) {\n cc2 = input.charCodeAt(chunkerCurrentIndex);\n if (cc2 > 96) { continue; }\n if (cc2 == cc) { matched = 1; break; }\n if (cc2 == 92) { // \\\n if (chunkerCurrentIndex == len - 1) {\n return fail('unescaped `\\\\`', chunkerCurrentIndex);\n }\n chunkerCurrentIndex++;\n }\n }\n if (matched) { continue; }\n return fail(`unmatched \\`${String.fromCharCode(cc)}\\``, currentChunkStartIndex);\n case 47: // /, check for comment\n if (parenLevel || (chunkerCurrentIndex == len - 1)) { continue; }\n cc2 = input.charCodeAt(chunkerCurrentIndex + 1);\n if (cc2 == 47) {\n // //, find lnfeed\n for (chunkerCurrentIndex = chunkerCurrentIndex + 2; chunkerCurrentIndex < len; chunkerCurrentIndex++) {\n cc2 = input.charCodeAt(chunkerCurrentIndex);\n if ((cc2 <= 13) && ((cc2 == 10) || (cc2 == 13))) { break; }\n }\n } else if (cc2 == 42) {\n // /*, find */\n lastMultiComment = currentChunkStartIndex = chunkerCurrentIndex;\n for (chunkerCurrentIndex = chunkerCurrentIndex + 2; chunkerCurrentIndex < len - 1; chunkerCurrentIndex++) {\n cc2 = input.charCodeAt(chunkerCurrentIndex);\n if (cc2 == 125) { lastMultiCommentEndBrace = chunkerCurrentIndex; }\n if (cc2 != 42) { continue; }\n if (input.charCodeAt(chunkerCurrentIndex + 1) == 47) { break; }\n }\n if (chunkerCurrentIndex == len - 1) {\n return fail('missing closing `*/`', currentChunkStartIndex);\n }\n chunkerCurrentIndex++;\n }\n continue;\n case 42: // *, check for unmatched */\n if ((chunkerCurrentIndex < len - 1) && (input.charCodeAt(chunkerCurrentIndex + 1) == 47)) {\n return fail('unmatched `/*`', chunkerCurrentIndex);\n }\n continue;\n }\n }\n\n if (level !== 0) {\n if ((lastMultiComment > lastOpening) && (lastMultiCommentEndBrace > lastMultiComment)) {\n return fail('missing closing `}` or `*/`', lastOpening);\n } else {\n return fail('missing closing `}`', lastOpening);\n }\n } else if (parenLevel !== 0) {\n return fail('missing closing `)`', lastOpeningParen);\n }\n\n emitChunk(true);\n return chunks;\n}\n"]}
var less = {
logLevel: 4,
errorReporting: 'console',
math: 'always',
strictUnits: false
};
describe('less.js legacy tests', function() {
testLessEqualsInDocument();
});

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet