less 4.4.1 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/Gruntfile.js +16 -17
  2. package/bin/lessc +35 -40
  3. package/dist/less.js +125 -186
  4. package/dist/less.min.js +2 -2
  5. package/dist/less.min.js.map +1 -1
  6. package/lib/less/contexts.js +0 -1
  7. package/lib/less/contexts.js.map +1 -1
  8. package/lib/less/default-options.js +22 -3
  9. package/lib/less/default-options.js.map +1 -1
  10. package/lib/less/less-error.js +2 -1
  11. package/lib/less/less-error.js.map +1 -1
  12. package/lib/less/parse-tree.js +58 -1
  13. package/lib/less/parse-tree.js.map +1 -1
  14. package/lib/less/parser/parser-input.js +2 -19
  15. package/lib/less/parser/parser-input.js.map +1 -1
  16. package/lib/less/parser/parser.js +8 -14
  17. package/lib/less/parser/parser.js.map +1 -1
  18. package/lib/less/source-map-output.js +1 -1
  19. package/lib/less/source-map-output.js.map +1 -1
  20. package/lib/less/tree/debug-info.js +29 -0
  21. package/lib/less/tree/debug-info.js.map +1 -1
  22. package/lib/less/tree/nested-at-rule.js +1 -1
  23. package/lib/less/tree/nested-at-rule.js.map +1 -1
  24. package/lib/less-node/lessc-helper.js +8 -7
  25. package/lib/less-node/lessc-helper.js.map +1 -1
  26. package/package.json +10 -5
  27. package/scripts/coverage-lines.js +207 -0
  28. package/scripts/coverage-report.js +158 -0
  29. package/scripts/postinstall.js +61 -0
  30. package/test/browser/common.js +22 -1
  31. package/test/browser/generator/runner.config.js +20 -24
  32. package/test/browser/generator/template.js +14 -3
  33. package/test/browser/runner-browser-options.js +5 -5
  34. package/test/index.js +293 -98
  35. package/test/less-test.js +453 -94
  36. package/test/sourcemaps/comprehensive.json +1 -0
  37. package/test/sourcemaps/sourcemaps-basepath.json +1 -0
  38. package/test/sourcemaps/sourcemaps-include-source.json +1 -0
  39. package/test/sourcemaps/sourcemaps-rootpath.json +1 -0
  40. package/test/sourcemaps/sourcemaps-url.json +1 -0
  41. package/test.less +1 -0
  42. package/tsconfig.json +20 -20
  43. package/lib/less/parser/chunker.js +0 -148
  44. package/lib/less/parser/chunker.js.map +0 -1
  45. package/test/browser/runner-legacy-options.js +0 -6
  46. package/test/browser/runner-legacy-spec.js +0 -3
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Generates a per-file coverage report table for src/ directories
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ const coverageSummaryPath = path.join(__dirname, '..', 'coverage', 'coverage-summary.json');
11
+
12
+ if (!fs.existsSync(coverageSummaryPath)) {
13
+ console.error('Coverage summary not found. Run pnpm test:coverage first.');
14
+ process.exit(1);
15
+ }
16
+
17
+ const coverage = JSON.parse(fs.readFileSync(coverageSummaryPath, 'utf8'));
18
+
19
+ // Filter to only src/ files (less, less-node) and bin/ files
20
+ // Note: src/less-browser/ is excluded because browser tests aren't included in coverage
21
+ // Abstract base classes are excluded as they're meant to be overridden by implementations
22
+ const abstractClasses = [
23
+ 'abstract-file-manager',
24
+ 'abstract-plugin-loader'
25
+ ];
26
+
27
+ const srcFiles = Object.entries(coverage)
28
+ .filter(([filePath]) => {
29
+ const normalized = filePath.replace(/\\/g, '/');
30
+ // Exclude abstract classes
31
+ if (abstractClasses.some(abstract => normalized.includes(abstract))) {
32
+ return false;
33
+ }
34
+ return (normalized.includes('/src/less/') && !normalized.includes('/src/less-browser/')) ||
35
+ normalized.includes('/src/less-node/') ||
36
+ normalized.includes('/bin/');
37
+ })
38
+ .map(([filePath, data]) => {
39
+ // Extract relative path from absolute path
40
+ const normalized = filePath.replace(/\\/g, '/');
41
+ // Match src/ paths or bin/ paths
42
+ const match = normalized.match(/((?:src\/[^/]+\/[^/]+\/|bin\/).+)$/);
43
+ const relativePath = match ? match[1] : path.basename(filePath);
44
+
45
+ return {
46
+ path: relativePath,
47
+ statements: data.statements,
48
+ branches: data.branches,
49
+ functions: data.functions,
50
+ lines: data.lines
51
+ };
52
+ })
53
+ .sort((a, b) => {
54
+ // Sort by directory first, then by coverage percentage
55
+ const pathCompare = a.path.localeCompare(b.path);
56
+ if (pathCompare !== 0) return pathCompare;
57
+ return a.statements.pct - b.statements.pct;
58
+ });
59
+
60
+ if (srcFiles.length === 0) {
61
+ console.log('No src/ files found in coverage report.');
62
+ process.exit(0);
63
+ }
64
+
65
+ // Group by directory
66
+ const grouped = {
67
+ 'src/less/': [],
68
+ 'src/less-node/': [],
69
+ 'bin/': []
70
+ };
71
+
72
+ srcFiles.forEach(file => {
73
+ if (file.path.startsWith('src/less/')) {
74
+ grouped['src/less/'].push(file);
75
+ } else if (file.path.startsWith('src/less-node/')) {
76
+ grouped['src/less-node/'].push(file);
77
+ } else if (file.path.startsWith('bin/')) {
78
+ grouped['bin/'].push(file);
79
+ }
80
+ });
81
+
82
+ // Print table
83
+ console.log('\n' + '='.repeat(100));
84
+ console.log('Per-File Coverage Report (src/less/, src/less-node/, and bin/)');
85
+ console.log('='.repeat(100));
86
+ console.log('For line-by-line coverage details, open coverage/index.html in your browser.');
87
+ console.log('='.repeat(100) + '\n');
88
+
89
+ Object.entries(grouped).forEach(([dir, files]) => {
90
+ if (files.length === 0) return;
91
+
92
+ console.log(`\n${dir.toUpperCase()}`);
93
+ console.log('-'.repeat(100));
94
+ console.log(
95
+ 'File'.padEnd(50) +
96
+ 'Statements'.padStart(12) +
97
+ 'Branches'.padStart(12) +
98
+ 'Functions'.padStart(12) +
99
+ 'Lines'.padStart(12)
100
+ );
101
+ console.log('-'.repeat(100));
102
+
103
+ files.forEach(file => {
104
+ const filename = file.path.replace(dir, '');
105
+ const truncated = filename.length > 48 ? '...' + filename.slice(-45) : filename;
106
+
107
+ console.log(
108
+ truncated.padEnd(50) +
109
+ `${file.statements.pct.toFixed(1)}%`.padStart(12) +
110
+ `${file.branches.pct.toFixed(1)}%`.padStart(12) +
111
+ `${file.functions.pct.toFixed(1)}%`.padStart(12) +
112
+ `${file.lines.pct.toFixed(1)}%`.padStart(12)
113
+ );
114
+ });
115
+
116
+ // Summary for this directory
117
+ const totals = files.reduce((acc, file) => {
118
+ acc.statements.total += file.statements.total;
119
+ acc.statements.covered += file.statements.covered;
120
+ acc.branches.total += file.branches.total;
121
+ acc.branches.covered += file.branches.covered;
122
+ acc.functions.total += file.functions.total;
123
+ acc.functions.covered += file.functions.covered;
124
+ acc.lines.total += file.lines.total;
125
+ acc.lines.covered += file.lines.covered;
126
+ return acc;
127
+ }, {
128
+ statements: { total: 0, covered: 0 },
129
+ branches: { total: 0, covered: 0 },
130
+ functions: { total: 0, covered: 0 },
131
+ lines: { total: 0, covered: 0 }
132
+ });
133
+
134
+ const stmtPct = totals.statements.total > 0
135
+ ? (totals.statements.covered / totals.statements.total * 100).toFixed(1)
136
+ : '0.0';
137
+ const branchPct = totals.branches.total > 0
138
+ ? (totals.branches.covered / totals.branches.total * 100).toFixed(1)
139
+ : '0.0';
140
+ const funcPct = totals.functions.total > 0
141
+ ? (totals.functions.covered / totals.functions.total * 100).toFixed(1)
142
+ : '0.0';
143
+ const linePct = totals.lines.total > 0
144
+ ? (totals.lines.covered / totals.lines.total * 100).toFixed(1)
145
+ : '0.0';
146
+
147
+ console.log('-'.repeat(100));
148
+ console.log(
149
+ 'TOTAL'.padEnd(50) +
150
+ `${stmtPct}%`.padStart(12) +
151
+ `${branchPct}%`.padStart(12) +
152
+ `${funcPct}%`.padStart(12) +
153
+ `${linePct}%`.padStart(12)
154
+ );
155
+ });
156
+
157
+ console.log('\n' + '='.repeat(100) + '\n');
158
+
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Post-install script for Less.js package
5
+ *
6
+ * This script installs Playwright browsers only when:
7
+ * 1. This is a development environment (not when installed as a dependency)
8
+ * 2. We're in a monorepo context (parent package.json exists)
9
+ * 3. Not running in CI or other automated environments
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const { execSync } = require('child_process');
15
+
16
+ // Check if we're in a development environment
17
+ function isDevelopmentEnvironment() {
18
+ // Skip if this is a global install or user config
19
+ if (process.env.npm_config_user_config || process.env.npm_config_global) {
20
+ return false;
21
+ }
22
+
23
+ // Skip in CI environments
24
+ if (process.env.CI || process.env.GITHUB_ACTIONS || process.env.TRAVIS) {
25
+ return false;
26
+ }
27
+
28
+ // Check if we're in a monorepo (parent package.json exists)
29
+ const parentPackageJson = path.join(__dirname, '../../../package.json');
30
+ if (!fs.existsSync(parentPackageJson)) {
31
+ return false;
32
+ }
33
+
34
+ // Check if this is the root of the monorepo
35
+ const currentPackageJson = path.join(__dirname, '../package.json');
36
+ if (!fs.existsSync(currentPackageJson)) {
37
+ return false;
38
+ }
39
+
40
+ return true;
41
+ }
42
+
43
+ // Install Playwright browsers
44
+ function installPlaywrightBrowsers() {
45
+ try {
46
+ console.log('🎭 Installing Playwright browsers for development...');
47
+ execSync('pnpm exec playwright install', {
48
+ stdio: 'inherit',
49
+ cwd: path.join(__dirname, '..')
50
+ });
51
+ console.log('✅ Playwright browsers installed successfully');
52
+ } catch (error) {
53
+ console.warn('⚠️ Failed to install Playwright browsers:', error.message);
54
+ console.warn(' You can install them manually with: pnpm exec playwright install');
55
+ }
56
+ }
57
+
58
+ // Main execution
59
+ if (isDevelopmentEnvironment()) {
60
+ installPlaywrightBrowsers();
61
+ }
@@ -99,6 +99,18 @@ testSheet = function (sheet) {
99
99
  window.navigator.userAgent.indexOf('Trident/') >= 0) {
100
100
  text = ieFormat(text);
101
101
  }
102
+
103
+ // Normalize URLs: convert absolute URLs back to relative for comparison
104
+ // The browser resolves relative URLs when reading from DOM, but we want to compare against the original relative URLs
105
+ lessOutput = lessOutput.replace(/url\("http:\/\/localhost:8081\/packages\/less\/node_modules\/@less\/test-data\/tests-unit\/([^"]+)"\)/g, 'url("$1")');
106
+ // Also normalize directory-prefixed relative URLs (e.g., "at-rules/myfont.woff2" -> "myfont.woff2")
107
+ // This happens because the browser resolves URLs relative to the HTML document location
108
+ lessOutput = lessOutput.replace(/url\("([a-z-]+\/)([^"]+)"\)/g, 'url("$2")');
109
+ // Also normalize @import statements that get resolved to absolute URLs
110
+ lessOutput = lessOutput.replace(/@import "http:\/\/localhost:8081\/packages\/less\/node_modules\/@less\/test-data\/tests-unit\/([^"]+)"(.*);/g, '@import "$1"$2;');
111
+ // Also normalize @import with directory prefix (e.g., "at-rules-keyword-comments/test.css" -> "test.css")
112
+ lessOutput = lessOutput.replace(/@import "([a-z-]+\/)([^"]+)"(.*);/g, '@import "$2"$3;');
113
+
102
114
  expect(lessOutput).to.equal(text);
103
115
  done();
104
116
  })
@@ -164,12 +176,21 @@ testErrorSheet = function (sheet) {
164
176
  .replace(/\nStack Trace\n[\s\S]*/i, '')
165
177
  .replace(/\n$/, '')
166
178
  .trim();
179
+ actualErrorMsg = actualErrorMsg
180
+ .replace(/ in [\w\-]+\.less( on line \d+, column \d+)?:?$/, '') // Remove filename and optional line/column from end of error message
181
+ .replace(/\{path\}/g, '')
182
+ .replace(/\{pathrel\}/g, '')
183
+ .replace(/\{pathhref\}/g, 'http://localhost:8081/packages/less/node_modules/@less/test-data/tests-error/eval/')
184
+ .replace(/\{404status\}/g, ' (404)')
185
+ .replace(/\{node\}[\s\S]*\{\/node\}/g, '')
186
+ .replace(/\n$/, '')
187
+ .trim();
167
188
  errorFile
168
189
  .then(function (errorTxt) {
169
190
  errorTxt = errorTxt
170
191
  .replace(/\{path\}/g, '')
171
192
  .replace(/\{pathrel\}/g, '')
172
- .replace(/\{pathhref\}/g, 'http://localhost:8081/test/less/errors/')
193
+ .replace(/\{pathhref\}/g, 'http://localhost:8081/packages/less/node_modules/@less/test-data/tests-error/eval/')
173
194
  .replace(/\{404status\}/g, ' (404)')
174
195
  .replace(/\{node\}[\s\S]*\{\/node\}/g, '')
175
196
  .replace(/\n$/, '')
@@ -4,21 +4,25 @@ var { forceCovertToBrowserPath } = require('./utils');
4
4
 
5
5
  /** Root of repo */
6
6
  var testFolder = forceCovertToBrowserPath(path.dirname(resolve.sync('@less/test-data')));
7
- var lessFolder = forceCovertToBrowserPath(path.join(testFolder, 'less'));
7
+ var testsUnitFolder = forceCovertToBrowserPath(path.join(testFolder, 'tests-unit'));
8
+ var testsConfigFolder = forceCovertToBrowserPath(path.join(testFolder, 'tests-config'));
8
9
  var localTests = forceCovertToBrowserPath(path.resolve(__dirname, '..'));
9
10
 
10
11
  module.exports = {
11
12
  main: {
12
13
  // src is used to build list of less files to compile
13
14
  src: [
14
- `${lessFolder}/_main/*.less`,
15
- `!${lessFolder}/_main/plugin-preeval.less`, // uses ES6 syntax
15
+ `${testsUnitFolder}/*/*.less`,
16
+ `!${testsUnitFolder}/plugin-preeval/plugin-preeval.less`, // uses ES6 syntax
16
17
  // Don't test NPM import, obviously
17
- `!${lessFolder}/_main/plugin-module.less`,
18
- `!${lessFolder}/_main/import-module.less`,
19
- `!${lessFolder}/_main/javascript.less`,
20
- `!${lessFolder}/_main/urls.less`,
21
- `!${lessFolder}/_main/empty.less`
18
+ `!${testsUnitFolder}/plugin-module/plugin-module.less`,
19
+ `!${testsUnitFolder}/import/import-module.less`,
20
+ `!${testsUnitFolder}/javascript/javascript.less`,
21
+ `!${testsUnitFolder}/urls/urls.less`,
22
+ `!${testsUnitFolder}/empty/empty.less`,
23
+ `!${testsUnitFolder}/color-functions/operations.less`, // conflicts with operations/operations.less
24
+ // Exclude debug line numbers tests - these are Node.js only (dumpLineNumbers is deprecated)
25
+ `!${testsConfigFolder}/debug/**/*.less`
22
26
  ],
23
27
  options: {
24
28
  helpers: 'test/browser/runner-main-options.js',
@@ -26,16 +30,8 @@ module.exports = {
26
30
  outfile: 'tmp/browser/test-runner-main.html'
27
31
  }
28
32
  },
29
- legacy: {
30
- src: [`${lessFolder}/legacy/*.less`],
31
- options: {
32
- helpers: 'test/browser/runner-legacy-options.js',
33
- specs: 'test/browser/runner-legacy-spec.js',
34
- outfile: 'tmp/browser/test-runner-legacy.html'
35
- }
36
- },
37
33
  strictUnits: {
38
- src: [`${lessFolder}/units/strict/*.less`],
34
+ src: [`${testsConfigFolder}/units/strict/*.less`],
39
35
  options: {
40
36
  helpers: 'test/browser/runner-strict-units-options.js',
41
37
  specs: 'test/browser/runner-strict-units-spec.js',
@@ -44,8 +40,8 @@ module.exports = {
44
40
  },
45
41
  errors: {
46
42
  src: [
47
- `${lessFolder}/errors/*.less`,
48
- `${testFolder}/errors/javascript-error.less`,
43
+ `${testFolder}/tests-error/eval/*.less`,
44
+ `${testFolder}/tests-error/parse/*.less`,
49
45
  `${localTests}/less/errors/*.less`
50
46
  ],
51
47
  options: {
@@ -56,7 +52,7 @@ module.exports = {
56
52
  }
57
53
  },
58
54
  noJsErrors: {
59
- src: [`${lessFolder}/no-js-errors/*.less`],
55
+ src: [`${testsConfigFolder}/no-js-errors/*.less`],
60
56
  options: {
61
57
  helpers: 'test/browser/runner-no-js-errors-options.js',
62
58
  specs: 'test/browser/runner-no-js-errors-spec.js',
@@ -141,7 +137,7 @@ module.exports = {
141
137
  }
142
138
  },
143
139
  postProcessorPlugin: {
144
- src: [`${lessFolder}/postProcessorPlugin/*.less`],
140
+ src: [`${testsConfigFolder}/postProcessorPlugin/*.less`],
145
141
  options: {
146
142
  helpers: [
147
143
  'test/plugins/postprocess/index.js',
@@ -153,7 +149,7 @@ module.exports = {
153
149
  }
154
150
  },
155
151
  preProcessorPlugin: {
156
- src: [`${lessFolder}/preProcessorPlugin/*.less`],
152
+ src: [`${testsConfigFolder}/preProcessorPlugin/*.less`],
157
153
  options: {
158
154
  helpers: [
159
155
  'test/plugins/preprocess/index.js',
@@ -164,7 +160,7 @@ module.exports = {
164
160
  }
165
161
  },
166
162
  visitorPlugin: {
167
- src: [`${lessFolder}/visitorPlugin/*.less`],
163
+ src: [`${testsConfigFolder}/visitorPlugin/*.less`],
168
164
  options: {
169
165
  helpers: [
170
166
  'test/plugins/visitor/index.js',
@@ -175,7 +171,7 @@ module.exports = {
175
171
  }
176
172
  },
177
173
  filemanagerPlugin: {
178
- src: [`${lessFolder}/filemanagerPlugin/*.less`],
174
+ src: [`${testsConfigFolder}/filemanagerPlugin/*.less`],
179
175
  options: {
180
176
  helpers: [
181
177
  'test/plugins/filemanager/index.js',
@@ -25,9 +25,20 @@ module.exports = (stylesheets, helpers, spec, less) => {
25
25
  <!-- for each test, generate CSS/LESS link tags -->
26
26
  $${stylesheets.map(function(fullLessName) {
27
27
  var pathParts = fullLessName.split('/');
28
- var fullCssName = fullLessName
29
- .replace(/\/(browser|test-data)\/less\//g, '/$1/css/')
30
- .replace(/less$/, 'css')
28
+ var fullCssName = fullLessName.replace(/less$/, 'css');
29
+
30
+ // Check if the CSS file exists in the same directory as the LESS file
31
+ var fs = require('fs');
32
+ var cssExists = fs.existsSync(fullCssName);
33
+
34
+ // If not, try the css/ directory for local browser tests
35
+ if (!cssExists && fullLessName.includes('/test/browser/less/')) {
36
+ var cssInCssDir = fullLessName.replace('/test/browser/less/', '/test/browser/css/').replace(/less$/, 'css');
37
+ if (fs.existsSync(cssInCssDir)) {
38
+ fullCssName = cssInCssDir;
39
+ }
40
+ }
41
+
31
42
  var lessName = pathParts[pathParts.length - 1];
32
43
  var name = lessName.split('.')[0];
33
44
  return `
@@ -6,7 +6,7 @@ var less = {
6
6
  };
7
7
 
8
8
  // test inline less in style tags by grabbing an assortment of less files and doing `@import`s
9
- var testFiles = ['charsets', 'colors', 'comments', 'css-3', 'strings', 'media', 'mixins'],
9
+ var testFiles = ['charsets/charsets', 'color-functions/basic', 'comments/comments', 'css-3/css-3', 'strings/strings', 'media/media', 'mixins/mixins'],
10
10
  testSheets = [];
11
11
 
12
12
  // setup style tags with less and link tags pointing to expected css output
@@ -14,13 +14,13 @@ var testFiles = ['charsets', 'colors', 'comments', 'css-3', 'strings', 'media',
14
14
  /**
15
15
  * @todo - generate the node_modules path for this file and in templates
16
16
  */
17
- var lessFolder = '../../node_modules/@less/test-data/less'
18
- var cssFolder = '../../node_modules/@less/test-data/css'
17
+ var lessFolder = '../../node_modules/@less/test-data/tests-unit'
18
+ var cssFolder = '../../node_modules/@less/test-data/tests-unit'
19
19
 
20
20
  for (var i = 0; i < testFiles.length; i++) {
21
21
  var file = testFiles[i],
22
- lessPath = lessFolder + '/_main/' + file + '.less',
23
- cssPath = cssFolder + '/_main/' + file + '.css',
22
+ lessPath = lessFolder + '/' + file + '.less',
23
+ cssPath = cssFolder + '/' + file + '.css',
24
24
  lessStyle = document.createElement('style'),
25
25
  cssLink = document.createElement('link'),
26
26
  lessText = '@import "' + lessPath + '";';