@jesscss/plugin-less-compat 2.0.0-alpha.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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +139 -0
  3. package/lib/index.d.ts +9 -0
  4. package/lib/index.js +10 -0
  5. package/lib/index.js.map +1 -0
  6. package/lib/less-compat-structures.d.ts +108 -0
  7. package/lib/less-compat-structures.js +519 -0
  8. package/lib/less-compat-structures.js.map +1 -0
  9. package/lib/nodes/at-rule.d.ts +6 -0
  10. package/lib/nodes/at-rule.js +72 -0
  11. package/lib/nodes/at-rule.js.map +1 -0
  12. package/lib/nodes/attribute-selector.d.ts +6 -0
  13. package/lib/nodes/attribute-selector.js +54 -0
  14. package/lib/nodes/attribute-selector.js.map +1 -0
  15. package/lib/nodes/call.d.ts +6 -0
  16. package/lib/nodes/call.js +83 -0
  17. package/lib/nodes/call.js.map +1 -0
  18. package/lib/nodes/color.d.ts +6 -0
  19. package/lib/nodes/color.js +57 -0
  20. package/lib/nodes/color.js.map +1 -0
  21. package/lib/nodes/combinator.d.ts +6 -0
  22. package/lib/nodes/combinator.js +34 -0
  23. package/lib/nodes/combinator.js.map +1 -0
  24. package/lib/nodes/comment.d.ts +6 -0
  25. package/lib/nodes/comment.js +41 -0
  26. package/lib/nodes/comment.js.map +1 -0
  27. package/lib/nodes/condition.d.ts +6 -0
  28. package/lib/nodes/condition.js +60 -0
  29. package/lib/nodes/condition.js.map +1 -0
  30. package/lib/nodes/declaration.d.ts +6 -0
  31. package/lib/nodes/declaration.js +81 -0
  32. package/lib/nodes/declaration.js.map +1 -0
  33. package/lib/nodes/dimension.d.ts +6 -0
  34. package/lib/nodes/dimension.js +47 -0
  35. package/lib/nodes/dimension.js.map +1 -0
  36. package/lib/nodes/expression.d.ts +6 -0
  37. package/lib/nodes/expression.js +44 -0
  38. package/lib/nodes/expression.js.map +1 -0
  39. package/lib/nodes/extend.d.ts +6 -0
  40. package/lib/nodes/extend.js +57 -0
  41. package/lib/nodes/extend.js.map +1 -0
  42. package/lib/nodes/import.d.ts +6 -0
  43. package/lib/nodes/import.js +63 -0
  44. package/lib/nodes/import.js.map +1 -0
  45. package/lib/nodes/index.d.ts +45 -0
  46. package/lib/nodes/index.js +308 -0
  47. package/lib/nodes/index.js.map +1 -0
  48. package/lib/nodes/keyword.d.ts +6 -0
  49. package/lib/nodes/keyword.js +36 -0
  50. package/lib/nodes/keyword.js.map +1 -0
  51. package/lib/nodes/list.d.ts +6 -0
  52. package/lib/nodes/list.js +150 -0
  53. package/lib/nodes/list.js.map +1 -0
  54. package/lib/nodes/mixin.d.ts +6 -0
  55. package/lib/nodes/mixin.js +62 -0
  56. package/lib/nodes/mixin.js.map +1 -0
  57. package/lib/nodes/negative.d.ts +6 -0
  58. package/lib/nodes/negative.js +42 -0
  59. package/lib/nodes/negative.js.map +1 -0
  60. package/lib/nodes/operation.d.ts +6 -0
  61. package/lib/nodes/operation.js +63 -0
  62. package/lib/nodes/operation.js.map +1 -0
  63. package/lib/nodes/paren.d.ts +6 -0
  64. package/lib/nodes/paren.js +42 -0
  65. package/lib/nodes/paren.js.map +1 -0
  66. package/lib/nodes/quoted.d.ts +6 -0
  67. package/lib/nodes/quoted.js +57 -0
  68. package/lib/nodes/quoted.js.map +1 -0
  69. package/lib/nodes/reference.d.ts +9 -0
  70. package/lib/nodes/reference.js +80 -0
  71. package/lib/nodes/reference.js.map +1 -0
  72. package/lib/nodes/ruleset.d.ts +6 -0
  73. package/lib/nodes/ruleset.js +108 -0
  74. package/lib/nodes/ruleset.js.map +1 -0
  75. package/lib/nodes/selector.d.ts +8 -0
  76. package/lib/nodes/selector.js +226 -0
  77. package/lib/nodes/selector.js.map +1 -0
  78. package/lib/nodes/sequence.d.ts +9 -0
  79. package/lib/nodes/sequence.js +75 -0
  80. package/lib/nodes/sequence.js.map +1 -0
  81. package/lib/nodes/url.d.ts +6 -0
  82. package/lib/nodes/url.js +42 -0
  83. package/lib/nodes/url.js.map +1 -0
  84. package/lib/nodes/var-declaration.d.ts +6 -0
  85. package/lib/nodes/var-declaration.js +60 -0
  86. package/lib/nodes/var-declaration.js.map +1 -0
  87. package/lib/plugin-utils.d.ts +20 -0
  88. package/lib/plugin-utils.js +100 -0
  89. package/lib/plugin-utils.js.map +1 -0
  90. package/lib/plugin.d.ts +92 -0
  91. package/lib/plugin.js +1027 -0
  92. package/lib/plugin.js.map +1 -0
  93. package/lib/transform/from-less.d.ts +30 -0
  94. package/lib/transform/from-less.js +170 -0
  95. package/lib/transform/from-less.js.map +1 -0
  96. package/lib/transform/index.d.ts +7 -0
  97. package/lib/transform/index.js +8 -0
  98. package/lib/transform/index.js.map +1 -0
  99. package/lib/transform/proxy.d.ts +32 -0
  100. package/lib/transform/proxy.js +138 -0
  101. package/lib/transform/proxy.js.map +1 -0
  102. package/lib/transform/to-less.d.ts +17 -0
  103. package/lib/transform/to-less.js +128 -0
  104. package/lib/transform/to-less.js.map +1 -0
  105. package/lib/transform/type-map.d.ts +27 -0
  106. package/lib/transform/type-map.js +105 -0
  107. package/lib/transform/type-map.js.map +1 -0
  108. package/lib/types.d.ts +33 -0
  109. package/lib/types.js +5 -0
  110. package/lib/types.js.map +1 -0
  111. package/package.json +56 -0
package/lib/plugin.js ADDED
@@ -0,0 +1,1027 @@
1
+ import * as fs from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import path from 'node:path';
4
+ import { AbstractPlugin, Any, Declaration, Dimension, F_VISIBLE } from '@jesscss/core';
5
+ import { toLessNode, fromLessNode, fromLessPluginReturnValue } from './transform/index.js';
6
+ import { getJessNodeFromProxy } from './transform/proxy.js';
7
+ import { filterPlugins } from './plugin-utils.js';
8
+ import { LessVisitor as LessVisitorClass, LessPluginManager, LessTreeConstructors, createLessMock } from './less-compat-structures.js';
9
+ /** Global key set by @jesscss/plugin-js when loaded. @plugin scripts require plugin-js (Deno) to be present. */
10
+ const JESS_PLUGIN_JS_GLOBAL = '__JESS_PLUGIN_JS_AVAILABLE__';
11
+ function assertPluginJsPresent() {
12
+ if (typeof globalThis === 'undefined' || !globalThis[JESS_PLUGIN_JS_GLOBAL]) {
13
+ throw new Error('@plugin script execution requires @jesscss/plugin-js (scripts must be run via Deno). Install @jesscss/plugin-js.');
14
+ }
15
+ }
16
+ const isThenable = (v) => !!v && (typeof v === 'object' || typeof v === 'function') && typeof v.then === 'function';
17
+ /**
18
+ * Wrap a Less plugin function and add it to a Jess function registry.
19
+ * Used so that @plugin-loaded functions register into the Rules that contain the @plugin.
20
+ * Conversion of Less return values to Jess uses the shared fromLessPluginReturnValue.
21
+ */
22
+ function addToJessRegistry(jessRegistry, name, func) {
23
+ if (!jessRegistry || typeof jessRegistry.add !== 'function') {
24
+ return;
25
+ }
26
+ try {
27
+ name = name.toLowerCase();
28
+ const wrapped = function (...args) {
29
+ const maybeEvaldArgs = args.map((arg) => {
30
+ if (arg instanceof Any || arg instanceof Declaration || arg instanceof Dimension) {
31
+ // Fast path for common nodes that are safe to eval normally via .eval
32
+ }
33
+ if (arg instanceof Object && arg && typeof arg.eval === 'function' && arg.evaluated !== true) {
34
+ try {
35
+ return arg.eval(this);
36
+ }
37
+ catch {
38
+ return arg;
39
+ }
40
+ }
41
+ return arg;
42
+ });
43
+ const call = (finalArgs) => func.apply(this, finalArgs);
44
+ const maybeNeedsAwait = maybeEvaldArgs.some(isThenable);
45
+ const result = maybeNeedsAwait
46
+ ? Promise.all(maybeEvaldArgs.map(a => isThenable(a) ? a : Promise.resolve(a))).then(call)
47
+ : call(maybeEvaldArgs);
48
+ const statementContext = this?.caller?.parent?.type === 'Rules';
49
+ const convertResult = (r) => fromLessPluginReturnValue(r, { statementContext });
50
+ return isThenable(result) ? result.then(convertResult) : convertResult(result);
51
+ };
52
+ Object.assign(wrapped, func);
53
+ jessRegistry.add(name, wrapped);
54
+ }
55
+ catch (e) {
56
+ void e;
57
+ }
58
+ }
59
+ /**
60
+ * Plugin that enables Less.js compatibility by transforming
61
+ * Jess nodes to Less-compatible format for visitor processing.
62
+ */
63
+ export class LessCompatPlugin extends AbstractPlugin {
64
+ opts;
65
+ name = 'less-compat';
66
+ // Cache the visitor instance so it's reused across multiple calls
67
+ // This ensures that visitors added via @plugin are available for subsequent nodes
68
+ _cachedVisitor;
69
+ _lessPluginManager;
70
+ _currentFilePath;
71
+ _jessFunctionRegistry;
72
+ constructor(opts = {}) {
73
+ super();
74
+ this.opts = opts;
75
+ }
76
+ /**
77
+ * Return the visitor as a preEval visitor so it runs before evaluation.
78
+ * This ensures @plugin directives are processed early, allowing their visitors
79
+ * to run on subsequent nodes during the preEval phase.
80
+ *
81
+ * Less plugins can register visitors via:
82
+ * - addVisitor() - these will run during preEval (default)
83
+ * - addPreProcessor() - these will run during preEval
84
+ * - addPostProcessor() - these will run during postEval (after evaluation)
85
+ */
86
+ get preEvalVisitor() {
87
+ // Cache the visitor instance so it's reused across multiple calls
88
+ // This ensures that visitors added via @plugin are available for subsequent nodes
89
+ if (!this._cachedVisitor) {
90
+ this._cachedVisitor = this.visitor;
91
+ }
92
+ return this._cachedVisitor;
93
+ }
94
+ /**
95
+ * Return postEval visitors from Less plugins that registered via addPostProcessor.
96
+ * These visitors will run after node.eval() completes.
97
+ */
98
+ get postEvalVisitor() {
99
+ // Not used yet - post processors run via runPostProcessors()
100
+ return undefined;
101
+ }
102
+ runPostProcessors(css, extra = {}) {
103
+ const processors = this._lessPluginManager?.getPostProcessors() || [];
104
+ return processors.reduce((current, processor) => {
105
+ if (!processor || typeof processor.process !== 'function') {
106
+ return current;
107
+ }
108
+ try {
109
+ const output = processor.process(current, extra);
110
+ return typeof output === 'string' ? output : current;
111
+ }
112
+ catch (error) {
113
+ throw error;
114
+ }
115
+ }, css);
116
+ }
117
+ setCurrentFilePath(filePath) {
118
+ this._currentFilePath = filePath;
119
+ }
120
+ setContext(context) {
121
+ try {
122
+ const root = context?.root;
123
+ if (root && typeof root.getRegistry === 'function') {
124
+ this._jessFunctionRegistry = root.getRegistry('function');
125
+ }
126
+ }
127
+ catch {
128
+ // ignore
129
+ }
130
+ }
131
+ /**
132
+ * Filter and separate Less plugins from Jess plugins
133
+ * This allows mixed plugin arrays to be handled correctly
134
+ *
135
+ * @deprecated Use filterPlugins from './plugin-utils.js' instead
136
+ */
137
+ static filterLessPlugins(plugins) {
138
+ return filterPlugins(plugins);
139
+ }
140
+ /**
141
+ * Return a visitor that wraps Less visitors
142
+ *
143
+ * This visitor intercepts each node, converts it to Less format,
144
+ * runs the Less visitors, and converts back if modified.
145
+ */
146
+ get visitor() {
147
+ const cache = this.opts.cache !== false;
148
+ const cacheMap = cache ? new WeakMap() : undefined;
149
+ // Use our own Less.js-compatible structures (no dependency on actual Less.js library)
150
+ const LessVisitor = LessVisitorClass;
151
+ const LessPluginManagerClass = LessPluginManager;
152
+ // Collect all Less visitors
153
+ // Use an array that we'll iterate as an iterator to allow dynamic insertion
154
+ const lessVisitorInstances = [];
155
+ // References for @plugin processing - initialized early so visitor can access them
156
+ let pluginManagerRef = null;
157
+ let mockLessRef = null;
158
+ // Create an iterator function that allows dynamic visitor insertion
159
+ // This matches Less.js behavior where @plugin can add visitors during traversal
160
+ function* createVisitorIterator() {
161
+ let index = 0;
162
+ while (index < lessVisitorInstances.length) {
163
+ yield lessVisitorInstances[index];
164
+ index++;
165
+ // Check again after incrementing - new visitors might have been added
166
+ // This allows @plugin to insert visitors that will be processed on subsequent nodes
167
+ }
168
+ }
169
+ // Always create mockLess and pluginManager for @plugin processing
170
+ // Even if there are no initial plugins, @plugin directives might load plugins
171
+ let currentRealRegistry = this._jessFunctionRegistry;
172
+ const createFunctionRegistry = () => {
173
+ // Internal storage (matching Less.js _data structure)
174
+ const data = {};
175
+ let base = null;
176
+ const registry = {
177
+ get _data() {
178
+ return data;
179
+ },
180
+ get _base() {
181
+ return base;
182
+ },
183
+ set _base(v) {
184
+ base = v;
185
+ },
186
+ // Less.js API methods
187
+ add(name, func) {
188
+ name = name.toLowerCase();
189
+ data[name] = func;
190
+ if (currentRealRegistry) {
191
+ addToJessRegistry(currentRealRegistry, name, func);
192
+ }
193
+ },
194
+ addMultiple(functions) {
195
+ Object.keys(functions).forEach((name) => {
196
+ this.add(name, functions[name]);
197
+ });
198
+ },
199
+ get(name) {
200
+ name = name.toLowerCase();
201
+ // Check local first
202
+ if (data[name]) {
203
+ return data[name];
204
+ }
205
+ // Then check base (for inheritance)
206
+ if (base) {
207
+ return base.get(name);
208
+ }
209
+ // If we have a real Jess registry, try that
210
+ if (currentRealRegistry) {
211
+ try {
212
+ return currentRealRegistry.get(name);
213
+ }
214
+ catch (_e) {
215
+ // Ignore errors
216
+ }
217
+ }
218
+ return undefined;
219
+ },
220
+ getLocalFunctions() {
221
+ return { ...data };
222
+ },
223
+ inherit() {
224
+ const child = createFunctionRegistry();
225
+ child._base = this;
226
+ return child;
227
+ },
228
+ create(base) {
229
+ const newRegistry = createFunctionRegistry();
230
+ newRegistry._base = base;
231
+ return newRegistry;
232
+ }
233
+ };
234
+ // Wrap with Proxy to handle edge cases (like 'Call' constructor access)
235
+ return new Proxy(registry, {
236
+ get(target, prop) {
237
+ // First check if it's a method on the registry
238
+ if (prop in target) {
239
+ return target[prop];
240
+ }
241
+ // Handle Less.js tree constructors that plugins might access
242
+ // These are typically accessed as functionRegistry.Call, functionRegistry.Variable, etc.
243
+ if (typeof prop === 'string' && /^[A-Z]/.test(prop)) {
244
+ // Try to get the constructor from our Less.js-compatible structures
245
+ if (LessTreeConstructors[prop]) {
246
+ return LessTreeConstructors[prop];
247
+ }
248
+ // Fallback: return a no-op constructor that matches Less.js structure
249
+ return function (...args) {
250
+ return {
251
+ value: null,
252
+ type: prop,
253
+ name: args[0] || '',
254
+ args: args.slice(1) || [],
255
+ index: 0,
256
+ fileInfo: {},
257
+ accept: function (visitor) {
258
+ return visitor.visit(this);
259
+ }
260
+ };
261
+ };
262
+ }
263
+ // For any other property, return a no-op function
264
+ return function () {
265
+ return { value: null };
266
+ };
267
+ }
268
+ });
269
+ };
270
+ /**
271
+ * Create a mock function registry that forwards add/addMultiple/get to the given
272
+ * Jess registry. Used when loading @plugin scripts so functions register into the
273
+ * Rules that contain the @plugin directive.
274
+ */
275
+ const createScopedFunctionRegistry = (jessRegistry) => {
276
+ const data = {};
277
+ const registry = {
278
+ add(name, func) {
279
+ name = name.toLowerCase();
280
+ data[name] = func;
281
+ addToJessRegistry(jessRegistry, name, func);
282
+ },
283
+ addMultiple(functions) {
284
+ Object.keys(functions).forEach((name) => {
285
+ this.add(name, functions[name]);
286
+ });
287
+ },
288
+ get(name) {
289
+ name = name.toLowerCase();
290
+ if (data[name]) {
291
+ return data[name];
292
+ }
293
+ try {
294
+ return jessRegistry?.get?.(name);
295
+ }
296
+ catch {
297
+ return undefined;
298
+ }
299
+ }
300
+ };
301
+ return new Proxy(registry, {
302
+ get(target, prop) {
303
+ if (prop in target) {
304
+ return target[prop];
305
+ }
306
+ if (typeof prop === 'string' && /^[A-Z]/.test(prop)) {
307
+ if (LessTreeConstructors[prop]) {
308
+ return LessTreeConstructors[prop];
309
+ }
310
+ return function (...args) {
311
+ return {
312
+ value: null,
313
+ type: prop,
314
+ name: args[0] || '',
315
+ args: args.slice(1) || [],
316
+ index: 0,
317
+ fileInfo: {},
318
+ accept: function (visitor) {
319
+ return visitor.visit(this);
320
+ }
321
+ };
322
+ };
323
+ }
324
+ return function () {
325
+ return { value: null };
326
+ };
327
+ }
328
+ });
329
+ };
330
+ const functionRegistry = createFunctionRegistry();
331
+ const mockLess = createLessMock(functionRegistry);
332
+ const pluginManager = new LessPluginManagerClass(mockLess, true);
333
+ this._lessPluginManager = pluginManager;
334
+ const loadPluginSource = (fullPath, registerPlugin, targetJessRegistry) => {
335
+ assertPluginJsPresent();
336
+ const contents = fs.readFileSync(fullPath, 'utf8');
337
+ const localModule = { exports: {} };
338
+ // When loading from an @plugin directive, pass a mock that registers to the Rules containing that @plugin
339
+ const functions = targetJessRegistry != null
340
+ ? createScopedFunctionRegistry(targetJessRegistry)
341
+ : functionRegistry;
342
+ const loader = new Function('module', 'require', 'registerPlugin', 'functions', 'tree', 'less', 'fileInfo', contents);
343
+ loader(localModule, createRequire(fullPath), registerPlugin, functions, LessTreeConstructors, mockLess, { filename: fullPath });
344
+ return {
345
+ module: localModule.exports,
346
+ registered: null
347
+ };
348
+ };
349
+ const requirePluginFile = (fullPath, targetJessRegistry) => {
350
+ const registeredPlugins = [];
351
+ const registerPlugin = (plugin) => {
352
+ registeredPlugins.push(plugin);
353
+ };
354
+ const loaded = loadPluginSource(fullPath, registerPlugin, targetJessRegistry);
355
+ return {
356
+ module: loaded.module,
357
+ registered: registeredPlugins.length > 0 ? registeredPlugins[registeredPlugins.length - 1] : loaded.registered
358
+ };
359
+ };
360
+ // Initialize references for @plugin processing
361
+ pluginManagerRef = pluginManager;
362
+ mockLessRef = mockLess;
363
+ // Handle Less plugins - call their install() method
364
+ if (this.opts.plugins?.length) {
365
+ // Separate Less plugins from Jess plugins
366
+ // Jess plugins are ignored here - they should be handled by the main plugin system
367
+ const { lessPlugins } = filterPlugins(this.opts.plugins);
368
+ if (lessPlugins.length > 0) {
369
+ // Track visitors before installation
370
+ const visitorsBefore = pluginManager.visitors ? [...pluginManager.visitors] : [];
371
+ // Process plugins - handle functions that need to be instantiated
372
+ // In JavaScript, you can call any function with 'new', so we always try that first
373
+ const processedPlugins = [];
374
+ lessPlugins.forEach((plugin) => {
375
+ if (!plugin) {
376
+ // Skip undefined/null plugins
377
+ return;
378
+ }
379
+ // If plugin is a function, try calling it with 'new' first
380
+ // This handles both constructors and regular functions
381
+ if (typeof plugin === 'function') {
382
+ try {
383
+ const pluginInstance = new plugin({});
384
+ if (pluginInstance) {
385
+ processedPlugins.push(pluginInstance);
386
+ }
387
+ else {
388
+ // If new returns undefined/null, try calling as function
389
+ try {
390
+ const pluginInstance2 = plugin({});
391
+ if (pluginInstance2) {
392
+ processedPlugins.push(pluginInstance2);
393
+ }
394
+ else {
395
+ // If both fail, use the function itself
396
+ processedPlugins.push(plugin);
397
+ }
398
+ }
399
+ catch (_e2) {
400
+ // If function call fails, use the function itself
401
+ processedPlugins.push(plugin);
402
+ }
403
+ }
404
+ }
405
+ catch (_e) {
406
+ // If 'new' fails, try calling as function
407
+ try {
408
+ const pluginInstance = plugin({});
409
+ if (pluginInstance) {
410
+ processedPlugins.push(pluginInstance);
411
+ }
412
+ else {
413
+ // If function call returns undefined, use the function itself
414
+ processedPlugins.push(plugin);
415
+ }
416
+ }
417
+ catch (_e2) {
418
+ // If both fail, use the function itself
419
+ processedPlugins.push(plugin);
420
+ }
421
+ }
422
+ }
423
+ else {
424
+ // Not a function, use as-is
425
+ processedPlugins.push(plugin);
426
+ }
427
+ });
428
+ processedPlugins.forEach((plugin) => {
429
+ if (!plugin) {
430
+ return;
431
+ }
432
+ // CRITICAL: Many Less plugins (like autoprefix, clean-css) are BOTH plugins AND visitors
433
+ // We should ALWAYS try to wrap the plugin instance as a visitor FIRST
434
+ // because the plugin instance itself IS the visitor, regardless of install()
435
+ try {
436
+ const wrappedVisitor = new LessVisitor(plugin);
437
+ lessVisitorInstances.push(wrappedVisitor);
438
+ }
439
+ catch (e) {
440
+ // If wrapping fails, log for debugging but continue
441
+ // The error might be expected if the plugin doesn't have visit* methods
442
+ // We'll try other methods below
443
+ void e;
444
+ }
445
+ // Check if it's a plugin with install method (most common case)
446
+ if (typeof plugin.install === 'function') {
447
+ // Call the plugin's install method directly
448
+ // This might add additional visitors via pluginManager.addVisitor()
449
+ plugin.install(mockLess, pluginManager, mockLess.functions.functionRegistry);
450
+ }
451
+ else if (typeof plugin === 'function') {
452
+ // Some plugins are constructor functions (like autoprefix, CleanCSS)
453
+ // Check if the constructor's prototype has install
454
+ if (plugin.prototype && typeof plugin.prototype.install === 'function') {
455
+ // It's a constructor - instantiate it
456
+ try {
457
+ const pluginInstance = new plugin({});
458
+ if (pluginInstance && typeof pluginInstance.install === 'function') {
459
+ pluginInstance.install(mockLess, pluginManager, mockLess.functions.functionRegistry);
460
+ }
461
+ }
462
+ catch (_e) {
463
+ // If constructor fails, try calling as a function
464
+ try {
465
+ const pluginInstance = plugin({});
466
+ if (pluginInstance && typeof pluginInstance.install === 'function') {
467
+ pluginInstance.install(mockLess, pluginManager, mockLess.functions.functionRegistry);
468
+ }
469
+ else if (pluginInstance) {
470
+ // Might be a visitor implementation directly
471
+ lessVisitorInstances.push(new LessVisitor(pluginInstance));
472
+ }
473
+ }
474
+ catch (_e2) {
475
+ // If all else fails, try wrapping it as a visitor
476
+ lessVisitorInstances.push(new LessVisitor(plugin));
477
+ }
478
+ }
479
+ }
480
+ else {
481
+ // Try calling as a function that returns a plugin instance
482
+ try {
483
+ const pluginInstance = plugin({});
484
+ if (pluginInstance && typeof pluginInstance.install === 'function') {
485
+ pluginInstance.install(mockLess, pluginManager, mockLess.functions.functionRegistry);
486
+ }
487
+ else if (pluginInstance) {
488
+ lessVisitorInstances.push(new LessVisitor(pluginInstance));
489
+ }
490
+ }
491
+ catch (_e2) {
492
+ // If all else fails, try wrapping it as a visitor
493
+ lessVisitorInstances.push(new LessVisitor(plugin));
494
+ }
495
+ }
496
+ }
497
+ else {
498
+ // Plugin might be a visitor implementation directly
499
+ lessVisitorInstances.push(new LessVisitor(plugin));
500
+ }
501
+ });
502
+ // Collect visitors added by plugins via addVisitor
503
+ // Some plugins add visitors during install() via pluginManager.addVisitor()
504
+ if (pluginManager.visitors && pluginManager.visitors.length > visitorsBefore.length) {
505
+ const newVisitors = pluginManager.visitors.slice(visitorsBefore.length);
506
+ // Wrap raw visitors in LessVisitor instances
507
+ lessVisitorInstances.push(...newVisitors.map((v) => {
508
+ // If it's already a LessVisitor, use it directly
509
+ if (v instanceof LessVisitor) {
510
+ return v;
511
+ }
512
+ // Otherwise, wrap it
513
+ return new LessVisitor(v);
514
+ }));
515
+ }
516
+ // References are already set above (before the if block)
517
+ }
518
+ }
519
+ // Handle direct visitors (advanced usage)
520
+ /** @deprecated Less.js Visitor API - Direct visitor support for Less.js compatibility */
521
+ if (this.opts.visitors?.length) {
522
+ const lessVisitors = this.opts.visitors;
523
+ lessVisitors.forEach((visitorImpl) => {
524
+ // If it's already a Visitor instance, use it
525
+ if (visitorImpl instanceof LessVisitor) {
526
+ lessVisitorInstances.push(visitorImpl);
527
+ }
528
+ else {
529
+ // Otherwise, wrap the implementation in a Visitor
530
+ lessVisitorInstances.push(new LessVisitor(visitorImpl));
531
+ }
532
+ });
533
+ }
534
+ // Don't return undefined even if there are no initial visitors
535
+ // @plugin directives might add visitors during traversal
536
+ // We'll create a visitor that can handle @plugin processing
537
+ // If there are truly no visitors and no @plugin directives, the visitor will just pass through
538
+ // Track nodes currently being processed to prevent infinite loops
539
+ // This set persists for the entire visitor lifetime to prevent re-processing
540
+ const processing = new WeakSet();
541
+ // Track if we're currently inside a Less visitor traversal
542
+ // This prevents the plugin visitor from being triggered when visitArray calls visit()
543
+ let insideLessTraversal = false;
544
+ // Jess runs pre-eval visitors in two passes; ensure we only process each @plugin directive once.
545
+ const processedPluginDirectives = new WeakSet();
546
+ // Create a visitor object that implements the Visitor interface
547
+ const visitor = {
548
+ // Handle @plugin at-rules - these should be processed early (like Less.js preEval)
549
+ // In Less.js, @plugin is processed in preEval phase before the tree is evaluated
550
+ // This ensures plugins loaded via @plugin have their visitors available for subsequent nodes
551
+ atRule: (node, _ctx) => {
552
+ // Check if this is a @plugin directive
553
+ // In Less.js, @plugin syntax is: @plugin "plugin-name";
554
+ // Handle both AtRule (modern) and Directive (v2) node types
555
+ if (node && (node.type === 'AtRule' || node.type === 'Directive')) {
556
+ const atRuleName = node.value?.name;
557
+ let nameValue;
558
+ // Extract name value (could be string or node)
559
+ if (typeof atRuleName === 'string') {
560
+ nameValue = atRuleName;
561
+ }
562
+ else if (atRuleName?.value) {
563
+ nameValue = atRuleName.value;
564
+ }
565
+ else if (atRuleName?.type === 'Any' && atRuleName.value) {
566
+ nameValue = atRuleName.value;
567
+ }
568
+ // Check if this is a @plugin directive
569
+ // The name will be '@plugin' (with @ prefix) or 'plugin' (without)
570
+ // Less.js uses '@plugin' but we should handle both
571
+ const isPlugin = nameValue === 'plugin' || nameValue === '@plugin';
572
+ if (isPlugin) {
573
+ const pluginDirectiveNode = (getJessNodeFromProxy(node) || node);
574
+ if (processedPluginDirectives.has(pluginDirectiveNode)) {
575
+ return node;
576
+ }
577
+ processedPluginDirectives.add(pluginDirectiveNode);
578
+ const baseDir = this._currentFilePath ? path.dirname(this._currentFilePath) : undefined;
579
+ // Extract plugin path/name and options from prelude
580
+ // Handle both AtRule (value.prelude) and Directive (value.value) structures
581
+ // Less.js syntax: @plugin (options) "path"
582
+ const prelude = node.value?.prelude || node.value?.value;
583
+ let pluginPath;
584
+ let pluginOptions;
585
+ if (prelude) {
586
+ // Helper to extract string value from a node (Quoted, Url, or string)
587
+ const extractStringValue = (node) => {
588
+ if (!node) {
589
+ return undefined;
590
+ }
591
+ if (typeof node === 'string') {
592
+ return node;
593
+ }
594
+ if (node.type === 'Quoted' && node.value) {
595
+ // Quoted.value can be string | Any | Interpolated
596
+ if (typeof node.value === 'string') {
597
+ return node.value;
598
+ }
599
+ if (node.value?.value && typeof node.value.value === 'string') {
600
+ return node.value.value;
601
+ }
602
+ // Try valueOf() for Any nodes
603
+ if (typeof node.valueOf === 'function') {
604
+ const val = node.valueOf();
605
+ if (typeof val === 'string') {
606
+ return val;
607
+ }
608
+ }
609
+ }
610
+ if (node.type === 'Url' && node.value) {
611
+ // Url.value can be Quoted, string, or other
612
+ if (typeof node.value === 'string') {
613
+ return node.value;
614
+ }
615
+ if (node.value.type === 'Quoted' && node.value.value) {
616
+ if (typeof node.value.value === 'string') {
617
+ return node.value.value;
618
+ }
619
+ if (node.value.value?.value && typeof node.value.value.value === 'string') {
620
+ return node.value.value.value;
621
+ }
622
+ }
623
+ // Try valueOf() for Url nodes
624
+ if (typeof node.valueOf === 'function') {
625
+ const val = node.valueOf();
626
+ if (typeof val === 'string') {
627
+ return val;
628
+ }
629
+ }
630
+ }
631
+ return undefined;
632
+ };
633
+ // Prelude might contain options in parentheses followed by the plugin path
634
+ // Less.js syntax: @plugin (options) "path"
635
+ // The prelude might be a Sequence with options and path, or just the path
636
+ if (typeof prelude === 'string') {
637
+ pluginPath = prelude;
638
+ }
639
+ else if (prelude.type === 'Quoted' || prelude.type === 'Url') {
640
+ pluginPath = extractStringValue(prelude);
641
+ }
642
+ else if (prelude.type === 'Sequence' && Array.isArray(prelude.value)) {
643
+ // Sequence might contain: [options in parens, quoted path]
644
+ // Look for Quoted or Url (the path) and any preceding options
645
+ for (let i = 0; i < prelude.value.length; i++) {
646
+ const item = prelude.value[i];
647
+ if (item && (item.type === 'Quoted' || item.type === 'Url')) {
648
+ pluginPath = extractStringValue(item);
649
+ // Check if there's an options node before this (e.g., in parentheses)
650
+ if (i > 0) {
651
+ const prevItem = prelude.value[i - 1];
652
+ // Options might be in a Paren node or as a string
653
+ if (prevItem && prevItem.type === 'Paren' && prevItem.value) {
654
+ const optionsValue = prevItem.value.valueOf ? prevItem.value.valueOf() : prevItem.value.toString();
655
+ if (typeof optionsValue === 'string') {
656
+ pluginOptions = optionsValue.trim();
657
+ }
658
+ }
659
+ else if (prevItem && typeof prevItem.valueOf === 'function') {
660
+ const optionsValue = prevItem.valueOf();
661
+ if (typeof optionsValue === 'string' && optionsValue.includes('=')) {
662
+ pluginOptions = optionsValue.trim();
663
+ }
664
+ }
665
+ }
666
+ if (pluginPath) {
667
+ break;
668
+ }
669
+ }
670
+ }
671
+ }
672
+ else if (prelude.type === 'Expression' && prelude.value) {
673
+ // Expression might contain options and a Quoted or Url node
674
+ const values = Array.isArray(prelude.value) ? prelude.value : [prelude.value];
675
+ for (let i = 0; i < values.length; i++) {
676
+ const item = values[i];
677
+ if (item && (item.type === 'Quoted' || item.type === 'Url')) {
678
+ pluginPath = extractStringValue(item);
679
+ // Check for options before the path
680
+ if (i > 0) {
681
+ const prevItem = values[i - 1];
682
+ if (prevItem && prevItem.type === 'Paren' && prevItem.value) {
683
+ const optionsValue = prevItem.value.valueOf ? prevItem.value.valueOf() : prevItem.value.toString();
684
+ if (typeof optionsValue === 'string') {
685
+ pluginOptions = optionsValue.trim();
686
+ }
687
+ }
688
+ }
689
+ if (pluginPath) {
690
+ break;
691
+ }
692
+ }
693
+ }
694
+ }
695
+ else if (prelude.type === 'List' && prelude.value) {
696
+ // List might contain options and path
697
+ const items = Array.isArray(prelude.value) ? prelude.value : [prelude.value];
698
+ for (let i = 0; i < items.length; i++) {
699
+ const item = items[i];
700
+ if (item && (item.type === 'Quoted' || item.type === 'Url')) {
701
+ pluginPath = extractStringValue(item);
702
+ // Check for options before the path
703
+ if (i > 0) {
704
+ const prevItem = items[i - 1];
705
+ if (prevItem && prevItem.type === 'Paren' && prevItem.value) {
706
+ const optionsValue = prevItem.value.valueOf ? prevItem.value.valueOf() : prevItem.value.toString();
707
+ if (typeof optionsValue === 'string') {
708
+ pluginOptions = optionsValue.trim();
709
+ }
710
+ }
711
+ }
712
+ if (pluginPath) {
713
+ break;
714
+ }
715
+ }
716
+ }
717
+ }
718
+ else if (prelude.value && typeof prelude.value === 'string') {
719
+ // Fallback: direct string value
720
+ pluginPath = prelude.value;
721
+ }
722
+ }
723
+ if (pluginPath) {
724
+ // Ensure pluginPath is a string and remove quotes if present
725
+ if (typeof pluginPath === 'string') {
726
+ pluginPath = pluginPath.replace(/^["']|["']$/g, '');
727
+ }
728
+ else {
729
+ // If it's not a string, try to convert it
730
+ pluginPath = String(pluginPath).replace(/^["']|["']$/g, '');
731
+ }
732
+ const isExplicitLocalPath = pluginPath.startsWith('.')
733
+ || pluginPath.startsWith('/')
734
+ || pluginPath.includes('/')
735
+ || pluginPath.includes(path.sep);
736
+ // Less.js will also resolve bare plugin names as local files relative to the current file,
737
+ // before falling back to npm package resolution. (Needed for test-data `plugin-transitive`.)
738
+ const localBasePath = (baseDir && !path.isAbsolute(pluginPath))
739
+ ? path.resolve(baseDir, pluginPath)
740
+ : (path.isAbsolute(pluginPath) ? pluginPath : undefined);
741
+ let resolvedLocalPluginFile;
742
+ if (localBasePath) {
743
+ const candidates = [
744
+ localBasePath,
745
+ `${localBasePath}.js`,
746
+ `${localBasePath}.cjs`,
747
+ `${localBasePath}.mjs`
748
+ ];
749
+ resolvedLocalPluginFile = candidates.find(p => fs.existsSync(p));
750
+ }
751
+ const isLocalPath = isExplicitLocalPath || !!resolvedLocalPluginFile;
752
+ // IMPORTANT: Less allows @plugin to be loaded multiple times in different scopes.
753
+ // Do NOT globally dedupe by pluginPath; this breaks Less's scoping rules.
754
+ if (pluginManagerRef && mockLessRef) {
755
+ try {
756
+ // Scope: register Less plugin functions into the nearest Rules scope,
757
+ // so nested @plugin shadowing matches Less.js behavior.
758
+ const scopeNode = getJessNodeFromProxy(node) || node;
759
+ let scopeRules = scopeNode;
760
+ while (scopeRules && scopeRules.type !== 'Rules') {
761
+ scopeRules = scopeRules.parent;
762
+ }
763
+ if (scopeRules && typeof scopeRules.getRegistry === 'function') {
764
+ // Root-level @plugin should behave as global registration (Less.js behavior),
765
+ // even when encountered in an imported file.
766
+ if (!scopeRules.parent) {
767
+ currentRealRegistry = this._jessFunctionRegistry;
768
+ }
769
+ else {
770
+ currentRealRegistry = scopeRules.getRegistry('function');
771
+ }
772
+ }
773
+ else {
774
+ currentRealRegistry = this._jessFunctionRegistry;
775
+ }
776
+ // Try to load plugin from registry (for testing and explicit registration)
777
+ let pluginInstance = null;
778
+ if (this.opts.pluginRegistry && this.opts.pluginRegistry[pluginPath]) {
779
+ const pluginFactory = this.opts.pluginRegistry[pluginPath];
780
+ pluginInstance = typeof pluginFactory === 'function' ? pluginFactory() : pluginFactory;
781
+ }
782
+ else if (isLocalPath && resolvedLocalPluginFile) {
783
+ const { module: pluginModule, registered } = requirePluginFile(resolvedLocalPluginFile, currentRealRegistry);
784
+ const PluginClass = pluginModule.default || pluginModule;
785
+ pluginInstance = registered || PluginClass;
786
+ }
787
+ else if (!isLocalPath && this.opts.autoLoadPlugins !== false) {
788
+ // Auto-load plugins from npm/node_modules (Less.js behavior)
789
+ // Expand plugin name to try multiple variations (e.g., "clean-css" -> ["clean-css", "less-plugin-clean-css"])
790
+ // Less.js tries prefixes: 'less-plugin-{name}' and '{name}'
791
+ const prefixes = ['less-plugin-', ''];
792
+ const packageNamesToTry = prefixes.map(prefix => prefix + pluginPath);
793
+ let loaded = false;
794
+ // Try to use node-modules plugin if available (synchronous resolution)
795
+ if (this.opts.nodeModulesPlugin) {
796
+ for (const packageName of packageNamesToTry) {
797
+ const resolvedPath = this.opts.nodeModulesPlugin.resolvePackage(packageName);
798
+ if (resolvedPath) {
799
+ try {
800
+ // Load the module using require
801
+ const { module: pluginModule, registered } = requirePluginFile(resolvedPath, currentRealRegistry);
802
+ // Get the plugin - could be default export or direct export
803
+ let PluginClass = pluginModule.default || pluginModule;
804
+ pluginInstance = registered || PluginClass;
805
+ loaded = true;
806
+ break;
807
+ }
808
+ catch { }
809
+ }
810
+ }
811
+ }
812
+ // Fallback to direct require() if node-modules plugin not available or didn't find it
813
+ if (!loaded) {
814
+ for (const fullName of packageNamesToTry) {
815
+ try {
816
+ // Try to require the plugin from node_modules
817
+ // This uses Node's module resolution (similar to Less.js)
818
+ if (typeof require !== 'undefined') {
819
+ const { module: pluginModule, registered } = requirePluginFile(fullName, currentRealRegistry);
820
+ // Get the plugin - could be default export or direct export
821
+ let PluginClass = pluginModule.default || pluginModule;
822
+ // Less.js pattern: if plugin is a function, instantiate it with new (no args)
823
+ // This matches Less.js's validatePlugin() behavior (line 138)
824
+ pluginInstance = registered || PluginClass;
825
+ loaded = true;
826
+ break;
827
+ }
828
+ }
829
+ catch (e) {
830
+ // Module not found - try next name
831
+ if (e.code !== 'MODULE_NOT_FOUND') {
832
+ // Some other error - continue trying alternative names.
833
+ }
834
+ }
835
+ }
836
+ }
837
+ }
838
+ else {
839
+ // Auto-loading disabled - skip
840
+ }
841
+ // If we have a plugin instance, register it synchronously
842
+ // This allows the plugin's visitors to be added to the iterator
843
+ // and they will be processed on subsequent nodes (Less.js behavior)
844
+ if (pluginInstance) {
845
+ // Less.js pattern: if plugin is a constructor function, instantiate it with `new` (no args)
846
+ if (typeof pluginInstance === 'function') {
847
+ try {
848
+ pluginInstance = new pluginInstance();
849
+ }
850
+ catch {
851
+ // ignore, fall back to using the function itself
852
+ }
853
+ }
854
+ // Set options if provided (Less.js pattern - see abstract-plugin-loader.js trySetOptions)
855
+ // Less.js calls setOptions both before and after addPlugin
856
+ if (pluginOptions && typeof pluginInstance.setOptions === 'function') {
857
+ try {
858
+ pluginInstance.setOptions(pluginOptions);
859
+ }
860
+ catch { }
861
+ }
862
+ const visitorsBefore = pluginManagerRef.visitors.length;
863
+ pluginManagerRef.registerPlugin(pluginInstance);
864
+ // Set options again after registration (Less.js pattern - plugin might have functions now)
865
+ if (pluginOptions && typeof pluginInstance.setOptions === 'function') {
866
+ try {
867
+ pluginInstance.setOptions(pluginOptions);
868
+ }
869
+ catch { }
870
+ }
871
+ // Collect any new visitors added by this plugin
872
+ // These will be automatically included in the iterator via .next() calls
873
+ if (pluginManagerRef.visitors.length > visitorsBefore) {
874
+ const newVisitors = pluginManagerRef.visitors.slice(visitorsBefore);
875
+ // Wrap raw visitors in LessVisitor instances
876
+ lessVisitorInstances.push(...newVisitors.map((v) => {
877
+ // If it's already a LessVisitor, use it directly
878
+ if (v instanceof LessVisitor) {
879
+ return v;
880
+ }
881
+ // Otherwise, wrap it
882
+ return new LessVisitor(v);
883
+ }));
884
+ }
885
+ }
886
+ }
887
+ catch (e) {
888
+ // Plugin loading failed - continue without it
889
+ void e;
890
+ }
891
+ }
892
+ }
893
+ // After processing @plugin directive (whether pluginPath was found or not),
894
+ // mark it as invisible so it doesn't appear in output
895
+ // This must happen for ALL @plugin directives, not just ones that successfully load
896
+ const jessNode = getJessNodeFromProxy(node) || node;
897
+ if (jessNode && typeof jessNode.removeFlag === 'function') {
898
+ jessNode.removeFlag(F_VISIBLE);
899
+ }
900
+ }
901
+ }
902
+ // Continue normal processing for all AtRules
903
+ return node;
904
+ },
905
+ visit: (node) => {
906
+ if (!node) {
907
+ return node;
908
+ }
909
+ // Get underlying Jess node if this is a Less proxy
910
+ // This allows us to check the processing WeakSet correctly
911
+ const jessNode = getJessNodeFromProxy(node) || node;
912
+ // CRITICAL: For AtRule nodes, we need to call atRule() FIRST to process @plugin directives
913
+ // before running Less visitors. Since our visitor is a plain object (not a class extending Visitor),
914
+ // visit() doesn't automatically call atRule() via _visit(). We need to call it manually.
915
+ if ((node.type === 'AtRule' || node.type === 'Directive') && visitor.atRule) {
916
+ // Call atRule() to process @plugin directives and add visitors
917
+ // This must happen before we run Less visitors, so that newly added visitors
918
+ // are available for subsequent nodes
919
+ const atRuleResult = visitor.atRule(node, undefined);
920
+ // Use the result if atRule() returned a different node (and it's not a symbol)
921
+ if (atRuleResult && typeof atRuleResult !== 'symbol' && atRuleResult !== node) {
922
+ node = atRuleResult;
923
+ // Update jessNode if node was replaced
924
+ const newJessNode = getJessNodeFromProxy(node) || node;
925
+ if (newJessNode !== jessNode) {
926
+ // If node was replaced, we need to update our reference
927
+ // But we'll continue with the original jessNode for processing tracking
928
+ }
929
+ }
930
+ }
931
+ // If we're already inside a Less visitor traversal, don't process again
932
+ // This prevents infinite loops when visitArray calls visit() on child nodes
933
+ if (insideLessTraversal) {
934
+ return node;
935
+ }
936
+ // Prevent recursion - if we're already processing this node, skip
937
+ // This prevents infinite loops when visitArray calls visit() on nodes that are already being processed
938
+ if (processing.has(jessNode)) {
939
+ return node;
940
+ }
941
+ // Mark as processing (keep in set for entire visitor lifetime)
942
+ processing.add(jessNode);
943
+ try {
944
+ // CRITICAL: For AtRule nodes, ensure @plugin is processed BEFORE running Less visitors
945
+ // In Jess's visitor system, atRule() is called as part of visit() via _visit(),
946
+ // but we need to ensure @plugin processing happens before Less visitors run.
947
+ // Since atRule() is a separate method that's called by the visitor system,
948
+ // we need to check if this is a @plugin directive and process it here too
949
+ // (as a fallback, in case atRule() hasn't been called yet).
950
+ // However, the normal flow should be: atRule() is called first, then visit().
951
+ // So we'll rely on atRule() to process @plugin, and visit() will run Less visitors.
952
+ // Convert Jess node to Less format (use underlying node if proxy)
953
+ const lessNode = toLessNode(jessNode, { cache: cacheMap });
954
+ // Mark that we're inside Less visitor traversal
955
+ // This prevents child nodes from triggering the plugin visitor again
956
+ insideLessTraversal = true;
957
+ try {
958
+ // Run all Less visitors using iterator pattern
959
+ // This allows new visitors to be added during iteration (e.g., from @plugin)
960
+ // and they will automatically be processed via .next() calls
961
+ let result = lessNode;
962
+ // Use iterator pattern - visitors can be inserted during iteration
963
+ // This matches Less.js behavior where @plugin-loaded visitors are inserted
964
+ // into the visitor chain and processed on subsequent nodes
965
+ // Only run visitors if we have any (including those added via @plugin)
966
+ if (lessVisitorInstances.length > 0) {
967
+ const visitorIterator = createVisitorIterator();
968
+ let iteratorResult = visitorIterator.next();
969
+ let iterationCount = 0;
970
+ while (!iteratorResult.done) {
971
+ iterationCount++;
972
+ if (iterationCount > 100) {
973
+ break;
974
+ }
975
+ const lessVisitor = iteratorResult.value;
976
+ // Less Visitor.visit() calls the appropriate visit* method
977
+ // Less.js Visitor class handles isReplacing internally:
978
+ // - If isReplacing=false: return value is ignored, node modified in place, returns original node
979
+ // - If isReplacing=true: return value replaces node (undefined = remove)
980
+ // @deprecated Less.js Visitor API - Using Less.js visitor pattern for compatibility
981
+ //
982
+ // Handle Less.js v2 "Directive" nodes - if node is Directive type,
983
+ // LessVisitor.visit() will route to visitDirective() which maps to visitAtRule()
984
+ result = lessVisitor.visit(result);
985
+ // If result is undefined, a replacing visitor wants to remove this node
986
+ // (Non-replacing visitors can't return undefined - Less.js ignores their return value)
987
+ if (result === undefined) {
988
+ return undefined;
989
+ }
990
+ // Get next visitor - if new visitors were added during this iteration,
991
+ // they will be included in subsequent .next() calls
992
+ // This allows @plugin-loaded visitors to be processed immediately
993
+ iteratorResult = visitorIterator.next();
994
+ }
995
+ }
996
+ // If visitor returned a different node, convert back to Jess
997
+ if (result !== lessNode) {
998
+ const converted = fromLessNode(result, { cache: cacheMap });
999
+ // Return converted node if it's different, otherwise return original
1000
+ return converted !== jessNode ? converted : jessNode;
1001
+ }
1002
+ return jessNode;
1003
+ }
1004
+ finally {
1005
+ // Unmark when Less visitor traversal is complete
1006
+ insideLessTraversal = false;
1007
+ }
1008
+ }
1009
+ finally {
1010
+ // Keep node in processing set - don't delete it
1011
+ // This ensures we never process the same node twice during the entire visitor run
1012
+ // The WeakSet will be garbage collected when the visitor is done
1013
+ }
1014
+ }
1015
+ };
1016
+ return visitor;
1017
+ }
1018
+ }
1019
+ /**
1020
+ * Create a Less.js compatibility plugin
1021
+ */
1022
+ const lessCompatPlugin = ((opts) => {
1023
+ return new LessCompatPlugin(opts);
1024
+ });
1025
+ export default lessCompatPlugin;
1026
+ export { lessCompatPlugin };
1027
+ //# sourceMappingURL=plugin.js.map