@octaviaflow/scss-generator 1.0.0

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.
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@octaviaflow/scss-generator",
3
+ "version": "1.0.0",
4
+ "license": "Apache-2.0",
5
+ "main": "src/index.js",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/octaviaflow-design-system.git",
9
+ "directory": "packages/scss-generator"
10
+ },
11
+ "bugs": "https://github.com/octaviaflow-design-system/issues",
12
+ "keywords": [
13
+ "octaviaflow",
14
+ "octaviaflow-design-system",
15
+ "scss-generator",
16
+ "react"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "dependencies": {
22
+ "prettier": "^3.3.3"
23
+ }
24
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Copyright OctaviaFlow
3
+ * Author: Vishal Kumar
4
+ * Created: 11/November/2025
5
+ *
6
+ * This source code is licensed under the Apache-2.0 license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+
10
+
11
+ 'use strict';
12
+
13
+ const { definitions } = require('../types');
14
+ const { createPrinter } = require('./printer');
15
+
16
+ function generate(ast) {
17
+ const printer = createPrinter(definitions);
18
+
19
+ printer.print(ast);
20
+
21
+ return {
22
+ code: printer.get(),
23
+ };
24
+ }
25
+
26
+ module.exports = {
27
+ generate,
28
+ };
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Copyright OctaviaFlow
3
+ * Author: Vishal Kumar
4
+ * Created: 11/November/2025
5
+ *
6
+ * This source code is licensed under the Apache-2.0 license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+
10
+
11
+ 'use strict';
12
+
13
+ const prettier = require('prettier2');
14
+
15
+ const prettierOptions = {
16
+ parser: 'scss',
17
+ printWidth: 80,
18
+ singleQuote: true,
19
+ trailingComma: 'es5',
20
+ proseWrap: 'always',
21
+ };
22
+
23
+ function createPrinter(definitions) {
24
+ let buffer = [];
25
+ let indentLevel = 0;
26
+
27
+ const printer = {
28
+ append(string) {
29
+ buffer.push(string);
30
+ },
31
+
32
+ blockStart(character = '{') {
33
+ printer.token(character);
34
+ indentLevel++;
35
+ printer.newline();
36
+ },
37
+
38
+ blockEnd(character = '}') {
39
+ indentLevel--;
40
+ printer.newline();
41
+ printer.token(character);
42
+ },
43
+
44
+ get() {
45
+ return prettier.format(buffer.join(''), prettierOptions);
46
+ },
47
+
48
+ maybeNewline() {
49
+ if (buffer[buffer.length - 1] !== '\n') {
50
+ printer.newline();
51
+ }
52
+ },
53
+
54
+ newline() {
55
+ buffer.push('\n');
56
+ buffer.push(padLeft(indentLevel));
57
+ },
58
+
59
+ print(node, parent) {
60
+ definitions[node.type].generate(printer, node, parent);
61
+ },
62
+
63
+ space() {
64
+ buffer.push(' ');
65
+ },
66
+
67
+ token(characters) {
68
+ return buffer.push(characters);
69
+ },
70
+ };
71
+
72
+ return printer;
73
+ }
74
+
75
+ function padLeft(level) {
76
+ return ' '.repeat(level);
77
+ }
78
+
79
+ module.exports = {
80
+ createPrinter,
81
+ };
package/src/index.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Copyright OctaviaFlow
3
+ * Author: Vishal Kumar
4
+ * Created: 11/November/2025
5
+ *
6
+ * This source code is licensed under the Apache-2.0 license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+
10
+
11
+ 'use strict';
12
+
13
+ const { definitions, types } = require('./types');
14
+ const { generate } = require('./generate');
15
+
16
+ module.exports = {
17
+ definitions,
18
+ generate,
19
+ types,
20
+ };
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Copyright OctaviaFlow
3
+ * Author: Vishal Kumar
4
+ * Created: 11/November/2025
5
+ *
6
+ * This source code is licensed under the Apache-2.0 license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+
10
+
11
+ 'use strict';
12
+
13
+ function noop() {}
14
+
15
+ function assertAny() {
16
+ return noop;
17
+ }
18
+
19
+ function assertDefined(node) {
20
+ if (!node) {
21
+ throw new Error(`Expected node of type ${node.type} to be defined`);
22
+ }
23
+ }
24
+
25
+ function assertValueType(expected) {
26
+ return (value) => {
27
+ if (typeof value !== expected) {
28
+ throw new TypeError(
29
+ `Expected value to be of type ${expected}, instead ` +
30
+ `received ${typeof value}`
31
+ );
32
+ }
33
+ };
34
+ }
35
+
36
+ function assertType({ type }) {
37
+ return (node) => {
38
+ assertDefined(node);
39
+
40
+ if (node.type !== type) {
41
+ throw new TypeError(
42
+ `Expected node to be of type ${type}, instead received: ` +
43
+ `${node.type}`
44
+ );
45
+ }
46
+ };
47
+ }
48
+
49
+ function assertOneOf(types) {
50
+ return (value, node) => {
51
+ const errors = [];
52
+ for (let i = 0; i < types.length; i++) {
53
+ try {
54
+ types[i](value);
55
+ return;
56
+ } catch (error) {
57
+ // Including this in case we have a program error instead of a TypeError
58
+ if (!(error instanceof TypeError)) {
59
+ throw error;
60
+ }
61
+
62
+ errors.push(error);
63
+ }
64
+ }
65
+
66
+ throw new TypeError(
67
+ `Expected node to match one of the expected types for ${node.type}.\n\n` +
68
+ errors.map((error) => error.message).join('\n') +
69
+ '\n'
70
+ );
71
+ };
72
+ }
73
+
74
+ function arrayOf(checkType) {
75
+ return (nodes = [], node) => {
76
+ for (let i = 0; i < nodes.length; i++) {
77
+ checkType(nodes[i], node);
78
+ }
79
+ };
80
+ }
81
+
82
+ module.exports = {
83
+ assertAny,
84
+ assertDefined,
85
+ assertOneOf,
86
+ assertType,
87
+ assertValueType,
88
+ arrayOf,
89
+ };
@@ -0,0 +1,831 @@
1
+ /**
2
+ * Copyright OctaviaFlow
3
+ * Author: Vishal Kumar
4
+ * Created: 11/November/2025
5
+ *
6
+ * This source code is licensed under the Apache-2.0 license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+
10
+
11
+ 'use strict';
12
+
13
+ const {
14
+ assertAny,
15
+ assertOneOf,
16
+ assertType,
17
+ assertValueType,
18
+ arrayOf,
19
+ } = require('./assert');
20
+ const { defineType } = require('./type');
21
+
22
+ //-------------------------------------------------------------------------------
23
+ // Comments
24
+ //-------------------------------------------------------------------------------
25
+ const Comment = defineType('Comment', {
26
+ fields: {
27
+ value: {
28
+ validate: assertValueType('string'),
29
+ },
30
+ },
31
+ generate(printer, node) {
32
+ const lines = node.value.split('\n');
33
+ for (let i = 0; i < lines.length; i++) {
34
+ printer.token('//');
35
+ printer.token(lines[i]);
36
+ if (i !== lines.length - 1) {
37
+ printer.newline();
38
+ }
39
+ }
40
+ },
41
+ });
42
+
43
+ //-------------------------------------------------------------------------------
44
+ // Identifier
45
+ //-------------------------------------------------------------------------------
46
+ const Identifier = defineType('Identifier', {
47
+ fields: {
48
+ name: {
49
+ validate: assertValueType('string'),
50
+ },
51
+ },
52
+ generate(printer, node, parent) {
53
+ if (
54
+ parent &&
55
+ (parent.type === Assignment.type ||
56
+ parent.type === AssignmentPattern.type ||
57
+ parent.type === CallExpression.type ||
58
+ parent.type === LogicalExpression.type ||
59
+ parent.type === SassMixin.type ||
60
+ parent.type === SassList.type ||
61
+ parent.type === RestPattern.type ||
62
+ parent.type === SassFunction.type)
63
+ ) {
64
+ printer.token('$');
65
+ }
66
+ printer.token(node.name);
67
+ },
68
+ });
69
+
70
+ //-------------------------------------------------------------------------------
71
+ // Blocks
72
+ //-------------------------------------------------------------------------------
73
+ const BlockStatement = defineType('BlockStatement', {
74
+ fields: {
75
+ body: {
76
+ validate: arrayOf(assertAny),
77
+ },
78
+ },
79
+ generate(printer, node) {
80
+ printer.blockStart();
81
+ for (let i = 0; i < node.body.length; i++) {
82
+ printer.print(node.body[i], node);
83
+ if (i !== node.body.length - 1) {
84
+ printer.newline();
85
+ }
86
+ }
87
+ printer.blockEnd();
88
+ },
89
+ });
90
+
91
+ //-------------------------------------------------------------------------------
92
+ // Values
93
+ //-------------------------------------------------------------------------------
94
+ const SassBoolean = defineType('SassBoolean', {
95
+ fields: {
96
+ value: {
97
+ validate: assertValueType('boolean'),
98
+ },
99
+ },
100
+ generate(printer, node) {
101
+ printer.token(node.value);
102
+ },
103
+ });
104
+
105
+ const SassColor = defineType('SassColor', {
106
+ fields: {
107
+ value: {
108
+ validate: assertValueType('string'),
109
+ },
110
+ },
111
+ generate(printer, node) {
112
+ printer.token(node.value);
113
+ },
114
+ });
115
+
116
+ const SassFunction = defineType('SassFunction', {
117
+ fields: {
118
+ id: {
119
+ validate: assertType(Identifier),
120
+ },
121
+ params: {
122
+ optional: true,
123
+ validate: () =>
124
+ arrayOf(
125
+ assertOneOf([assertType(AssignmentPattern), assertType(Identifier)])
126
+ ),
127
+ },
128
+ body: {
129
+ validate: () => assertType(BlockStatement),
130
+ },
131
+ },
132
+ generate(printer, node, parent) {
133
+ printer.token('@function');
134
+ printer.space();
135
+ printer.print(node.id, parent);
136
+ printer.token('(');
137
+
138
+ if (Array.isArray(node.params)) {
139
+ for (let i = 0; i < node.params.length; i++) {
140
+ printer.print(node.params[i], node);
141
+ if (i !== node.params.length - 1) {
142
+ printer.token(',');
143
+ printer.space();
144
+ }
145
+ }
146
+ }
147
+
148
+ printer.token(')');
149
+ printer.space();
150
+ printer.print(node.body, parent);
151
+ },
152
+ });
153
+
154
+ const SassList = defineType('SassList', {
155
+ fields: {
156
+ elements: {
157
+ validate: () =>
158
+ arrayOf(
159
+ assertOneOf([
160
+ assertType(SassBoolean),
161
+ assertType(SassList),
162
+ assertType(SassMap),
163
+ assertType(SassNumber),
164
+ assertType(SassString),
165
+ assertType(Identifier),
166
+ ])
167
+ ),
168
+ },
169
+ },
170
+ generate(printer, node) {
171
+ printer.token('(');
172
+ for (let i = 0; i < node.elements.length; i++) {
173
+ printer.print(node.elements[i], node);
174
+ if (i !== node.elements.length - 1) {
175
+ printer.token(',');
176
+ printer.space();
177
+ }
178
+ }
179
+ printer.token(')');
180
+ },
181
+ });
182
+
183
+ const SassMap = defineType('SassMap', {
184
+ fields: {
185
+ properties: {
186
+ validate: () => arrayOf(assertType(SassMapProperty)),
187
+ },
188
+ },
189
+ generate(printer, node) {
190
+ printer.blockStart('(');
191
+ for (let i = 0; i < node.properties.length; i++) {
192
+ printer.print(node.properties[i]);
193
+ if (i !== node.properties.length - 1) {
194
+ printer.token(',');
195
+ printer.newline();
196
+ }
197
+ }
198
+ printer.blockEnd(')');
199
+ },
200
+ });
201
+
202
+ const SassMapProperty = defineType('SassMapProperty', {
203
+ fields: {
204
+ key: {
205
+ validate: assertType(Identifier),
206
+ },
207
+ value: {
208
+ validate: () =>
209
+ assertOneOf([SassBoolean, SassNumber, SassString, SassList, SassMap]),
210
+ },
211
+ quoted: {
212
+ optional: true,
213
+ validate: assertValueType('boolean'),
214
+ },
215
+ },
216
+ generate(printer, node) {
217
+ if (node.quoted) {
218
+ printer.token(`'`);
219
+ printer.print(node.key, node);
220
+ printer.token(`'`);
221
+ } else {
222
+ printer.print(node.key, node);
223
+ }
224
+ printer.token(':');
225
+ printer.space();
226
+ printer.print(node.value, node);
227
+ },
228
+ });
229
+
230
+ const SassMixin = defineType('SassMixin', {
231
+ fields: {
232
+ id: {
233
+ validate: assertType(Identifier),
234
+ },
235
+ params: {
236
+ optional: true,
237
+ validate: () =>
238
+ arrayOf(
239
+ assertOneOf([assertType(AssignmentPattern), assertType(Identifier)])
240
+ ),
241
+ },
242
+ body: {
243
+ validate: assertType(BlockStatement),
244
+ },
245
+ },
246
+ generate(printer, node, parent) {
247
+ printer.token('@mixin');
248
+ printer.space();
249
+ printer.print(node.id, parent);
250
+ printer.token('(');
251
+
252
+ if (Array.isArray(node.params)) {
253
+ for (let i = 0; i < node.params.length; i++) {
254
+ printer.print(node.params[i], node);
255
+ if (i !== node.params.length - 1) {
256
+ printer.token(',');
257
+ printer.space();
258
+ }
259
+ }
260
+ }
261
+
262
+ printer.token(')');
263
+ printer.space();
264
+ printer.print(node.body, parent);
265
+ },
266
+ });
267
+
268
+ const SassNumber = defineType('SassNumber', {
269
+ fields: {
270
+ value: {
271
+ validate: assertValueType('number'),
272
+ },
273
+ },
274
+ generate(printer, node) {
275
+ printer.token(node.value);
276
+ },
277
+ });
278
+
279
+ const SassString = defineType('SassString', {
280
+ fields: {
281
+ value: {
282
+ validate: assertValueType('string'),
283
+ },
284
+ },
285
+ generate(printer, node) {
286
+ printer.token(`'${node.value}'`);
287
+ },
288
+ });
289
+
290
+ // Allow ability to shortcircuit AST builder limitations and embed raw values
291
+ // into the Sass source code
292
+ const SassValue = defineType('SassValue', {
293
+ fields: {
294
+ value: {
295
+ validate: assertAny,
296
+ },
297
+ },
298
+ generate(printer, node) {
299
+ printer.token(node.value);
300
+ },
301
+ });
302
+
303
+ //-------------------------------------------------------------------------------
304
+ // Calls
305
+ //-------------------------------------------------------------------------------
306
+ const SassFunctionCall = defineType('SassFunctionCall', {
307
+ fields: {
308
+ id: {
309
+ validate: assertType(Identifier),
310
+ },
311
+ params: {
312
+ optional: true,
313
+ validate: () =>
314
+ arrayOf(
315
+ assertOneOf([
316
+ assertType(Identifier),
317
+ assertType(SassBoolean),
318
+ assertType(SassList),
319
+ assertType(SassMap),
320
+ assertType(SassNumber),
321
+ assertType(SassString),
322
+ assertType(SassValue),
323
+ ])
324
+ ),
325
+ },
326
+ },
327
+ generate(printer, node) {
328
+ printer.space();
329
+ printer.print(node.id);
330
+ printer.token('(');
331
+ if (Array.isArray(node.params)) {
332
+ for (let i = 0; i < node.params.length; i++) {
333
+ const param = node.params[i];
334
+ if (param.type === Identifier.type) {
335
+ printer.token('$');
336
+ }
337
+ printer.print(param, node);
338
+ if (i !== node.params.length - 1) {
339
+ printer.token(',');
340
+ printer.space();
341
+ }
342
+ }
343
+ }
344
+ printer.token(')');
345
+ },
346
+ });
347
+
348
+ const SassMixinCall = defineType('SassMixinCall', {
349
+ fields: {
350
+ id: {
351
+ validate: assertType(Identifier),
352
+ },
353
+ params: {
354
+ optional: true,
355
+ validate: () =>
356
+ arrayOf(
357
+ assertOneOf([
358
+ assertType(Identifier),
359
+ assertType(SassBoolean),
360
+ assertType(SassList),
361
+ assertType(SassMap),
362
+ assertType(SassNumber),
363
+ assertType(SassString),
364
+ ])
365
+ ),
366
+ },
367
+ body: {
368
+ optional: true,
369
+ validate: assertType(BlockStatement),
370
+ },
371
+ },
372
+ generate(printer, node) {
373
+ printer.token('@include');
374
+ printer.space();
375
+ printer.print(node.id);
376
+
377
+ printer.token('(');
378
+ if (Array.isArray(node.params)) {
379
+ for (let i = 0; i < node.params.length; i++) {
380
+ const param = node.params[i];
381
+
382
+ if (param.type === Identifier.type) {
383
+ printer.token('$');
384
+ }
385
+
386
+ printer.print(param, node);
387
+ if (i !== node.params.length - 1) {
388
+ printer.token(',');
389
+ printer.space();
390
+ }
391
+ }
392
+ }
393
+ printer.token(')');
394
+
395
+ if (node.body) {
396
+ printer.print(node.body, node);
397
+ }
398
+
399
+ printer.token(';');
400
+ },
401
+ });
402
+
403
+ //-------------------------------------------------------------------------------
404
+ // Rules
405
+ //-------------------------------------------------------------------------------
406
+ const Rule = defineType('Rule', {
407
+ fields: {
408
+ declarations: {
409
+ validate: () => arrayOf(assertType(Declaration)),
410
+ },
411
+ selectors: {
412
+ validate: arrayOf(assertValueType('string')),
413
+ },
414
+ },
415
+ generate(printer, node) {
416
+ printer.token(node.selectors.join(', '));
417
+ printer.space();
418
+ printer.blockStart();
419
+
420
+ for (let i = 0; i < node.declarations.length; i++) {
421
+ const declaration = node.declarations[i];
422
+
423
+ printer.print(declaration, node);
424
+
425
+ if (i !== node.declarations.length - 1) {
426
+ printer.newline();
427
+ }
428
+ }
429
+
430
+ printer.blockEnd();
431
+ },
432
+ });
433
+
434
+ const Declaration = defineType('Declaration', {
435
+ fields: {
436
+ property: {
437
+ validate: assertValueType('string'),
438
+ },
439
+ value: {
440
+ validate: () =>
441
+ assertOneOf([assertValueType('string'), assertType(CallExpression)]),
442
+ },
443
+ },
444
+ generate(printer, node) {
445
+ printer.token(node.property);
446
+ printer.token(':');
447
+ printer.space();
448
+ if (typeof node.value === 'string') {
449
+ printer.token(node.value);
450
+ } else {
451
+ printer.print(node.value);
452
+ }
453
+ printer.token(';');
454
+ },
455
+ });
456
+
457
+ //-------------------------------------------------------------------------------
458
+ // At-Rules and directives
459
+ //-------------------------------------------------------------------------------
460
+ const AtRule = defineType('AtRule', {
461
+ fields: {
462
+ name: {
463
+ validate: assertValueType('string'),
464
+ },
465
+ media: {
466
+ validate: assertValueType('string'),
467
+ },
468
+ children: {
469
+ validate: arrayOf(assertOneOf([assertType(Rule)])),
470
+ },
471
+ },
472
+ generate(printer, node) {
473
+ printer.token(`@${node.name}`);
474
+ printer.space();
475
+ printer.token(node.media);
476
+ printer.space();
477
+ printer.blockStart();
478
+ for (let i = 0; i < node.children.length; i++) {
479
+ printer.print(node.children[i], node);
480
+ if (i !== node.children.length - 1) {
481
+ printer.newline();
482
+ }
483
+ }
484
+ printer.blockEnd();
485
+ },
486
+ });
487
+
488
+ const AtContent = defineType('AtContent', {
489
+ fields: {},
490
+ generate(printer, node, parent) {
491
+ if (parent.body.indexOf(node) !== 0) {
492
+ printer.maybeNewline();
493
+ }
494
+ printer.token('@content;');
495
+ },
496
+ });
497
+
498
+ const AtReturn = defineType('AtReturn', {
499
+ fields: {
500
+ argument: {
501
+ validate: assertAny,
502
+ },
503
+ },
504
+ generate(printer, node, parent) {
505
+ if (parent.body.indexOf(node) !== 0) {
506
+ printer.maybeNewline();
507
+ }
508
+ printer.token('@return');
509
+ printer.space();
510
+ printer.print(node.argument, node);
511
+ printer.token(';');
512
+ },
513
+ });
514
+
515
+ //-------------------------------------------------------------------------------
516
+ // Assignment
517
+ //-------------------------------------------------------------------------------
518
+ const Assignment = defineType('Assignment', {
519
+ fields: {
520
+ id: {
521
+ validate: assertType(Identifier),
522
+ },
523
+ init: {
524
+ validate: () =>
525
+ assertOneOf([
526
+ assertType(CallExpression),
527
+ assertType(SassBoolean),
528
+ assertType(SassColor),
529
+ assertType(SassList),
530
+ assertType(SassMap),
531
+ assertType(SassNumber),
532
+ assertType(SassString),
533
+ assertType(SassFunctionCall),
534
+ ]),
535
+ },
536
+ default: {
537
+ optional: true,
538
+ validate: assertValueType('boolean'),
539
+ },
540
+ global: {
541
+ optional: true,
542
+ validate: assertValueType('boolean'),
543
+ },
544
+ },
545
+ generate(printer, node, parent) {
546
+ printer.print(node.id, node);
547
+ printer.token(':');
548
+ printer.space();
549
+ printer.print(node.init, node);
550
+
551
+ if (node.default) {
552
+ printer.space();
553
+ printer.token('!default');
554
+
555
+ if (node.global) {
556
+ printer.space();
557
+ }
558
+ }
559
+
560
+ if (node.global) {
561
+ printer.token('!global');
562
+ }
563
+
564
+ printer.token(';');
565
+
566
+ if (parent) {
567
+ // We have a couple of options for the block we may be operating in, in
568
+ // this case we'll check for children or body and check if the collection
569
+ // exists
570
+ const collection = parent.children || parent.body;
571
+
572
+ // If we have a collection, and there are more than one element in the
573
+ // collection, then we can safely determine if we need to apply a newline
574
+ // after an assignment
575
+ if (collection && collection.length > 1) {
576
+ const assignments = collection.filter(
577
+ (node) => node.type === Assignment.type
578
+ );
579
+ if (
580
+ assignments.length === 1 ||
581
+ assignments.indexOf(node) === assignments.length - 1
582
+ ) {
583
+ printer.newline();
584
+ }
585
+ }
586
+ }
587
+ },
588
+ });
589
+
590
+ const AssignmentPattern = defineType('AssignmentPattern', {
591
+ fields: {
592
+ left: {
593
+ validate: assertType(Identifier),
594
+ },
595
+ right: {
596
+ validate: assertAny,
597
+ },
598
+ },
599
+ generate(printer, node) {
600
+ printer.print(node.left, node);
601
+ printer.token(':');
602
+ printer.space();
603
+ printer.print(node.right, node);
604
+ },
605
+ });
606
+
607
+ const RestPattern = defineType('RestPattern', {
608
+ fields: {
609
+ id: {
610
+ validate: assertType(Identifier),
611
+ },
612
+ },
613
+ generate(printer, node, parent) {
614
+ printer.print(node.id, parent);
615
+ printer.token('...');
616
+ },
617
+ });
618
+
619
+ //-------------------------------------------------------------------------------
620
+ // Imports
621
+ //-------------------------------------------------------------------------------
622
+ const SassImport = defineType('SassImport', {
623
+ fields: {
624
+ path: {
625
+ validate: assertValueType('string'),
626
+ },
627
+ },
628
+ generate(printer, node) {
629
+ printer.token('@import');
630
+ printer.space();
631
+ printer.token(`'${node.path}'`);
632
+ printer.token(';');
633
+ },
634
+ });
635
+
636
+ const SassModule = defineType('SassModule', {
637
+ fields: {
638
+ path: {
639
+ validate: assertValueType('string'),
640
+ },
641
+ },
642
+ generate(printer, node) {
643
+ printer.token('@use');
644
+ printer.space();
645
+ printer.token(`'${node.path}'`);
646
+ printer.token(';');
647
+ },
648
+ });
649
+
650
+ const SassForward = defineType('SassForward', {
651
+ fields: {
652
+ path: {
653
+ validate: assertValueType('string'),
654
+ },
655
+ },
656
+ generate(printer, node) {
657
+ printer.token('@forward');
658
+ printer.space();
659
+ printer.token(`'${node.path}'`);
660
+ printer.token(';');
661
+ },
662
+ });
663
+
664
+ //-------------------------------------------------------------------------------
665
+ // Control structures
666
+ //-------------------------------------------------------------------------------
667
+ const IfStatement = defineType('IfStatement', {
668
+ fields: {
669
+ test: {
670
+ validate: assertAny,
671
+ },
672
+ consequent: {
673
+ optional: true,
674
+ validate: assertType(BlockStatement),
675
+ },
676
+ alternate: {
677
+ optional: true,
678
+ validate: () =>
679
+ assertOneOf([assertType(IfStatement), assertType(BlockStatement)]),
680
+ },
681
+ },
682
+ generate(printer, node, parent) {
683
+ if (parent && parent.type === IfStatement.type) {
684
+ printer.space();
685
+ printer.token('if');
686
+ } else {
687
+ printer.token('@if');
688
+ }
689
+
690
+ printer.space();
691
+ printer.print(node.test, node);
692
+ printer.print(node.consequent, node);
693
+
694
+ if (node.alternate) {
695
+ printer.token('@else');
696
+ printer.print(node.alternate, node);
697
+ }
698
+ },
699
+ });
700
+
701
+ //-------------------------------------------------------------------------------
702
+ // Logical expressions
703
+ //-------------------------------------------------------------------------------
704
+ const LogicalExpression = defineType('LogicalExpression', {
705
+ fields: {
706
+ left: {
707
+ validate: assertAny,
708
+ },
709
+ operator: {
710
+ validate: assertValueType('string'),
711
+ },
712
+ right: {
713
+ validate: assertAny,
714
+ },
715
+ },
716
+ generate(printer, node) {
717
+ printer.print(node.left, node);
718
+ printer.space();
719
+ printer.token(node.operator);
720
+ printer.space();
721
+ printer.print(node.right, node);
722
+ },
723
+ });
724
+
725
+ //-------------------------------------------------------------------------------
726
+ // Call expressions
727
+ //-------------------------------------------------------------------------------
728
+ const CallExpression = defineType('CallExpression', {
729
+ fields: {
730
+ callee: {
731
+ validate: assertType(Identifier),
732
+ },
733
+ arguments: {
734
+ optional: true,
735
+ validate: arrayOf(assertAny),
736
+ },
737
+ },
738
+ generate(printer, node) {
739
+ printer.print(node.callee);
740
+ printer.token('(');
741
+ if (Array.isArray(node.arguments)) {
742
+ for (let i = 0; i < node.arguments.length; i++) {
743
+ printer.print(node.arguments[i], node);
744
+ if (i !== node.arguments.length - 1) {
745
+ printer.token(',');
746
+ printer.space();
747
+ }
748
+ }
749
+ }
750
+ printer.token(')');
751
+ },
752
+ });
753
+
754
+ //-------------------------------------------------------------------------------
755
+ // StyleSheet
756
+ //-------------------------------------------------------------------------------
757
+ const StyleSheet = defineType('StyleSheet', {
758
+ fields: {
759
+ children: {
760
+ validate: () =>
761
+ arrayOf(
762
+ assertOneOf([
763
+ assertType(Assignment),
764
+ assertType(AtRule),
765
+ assertType(Comment),
766
+ assertType(IfStatement),
767
+ assertType(Rule),
768
+ assertType(SassFunction),
769
+ assertType(SassImport),
770
+ assertType(SassMixin),
771
+ assertType(SassMixinCall),
772
+ assertType(Newline),
773
+ ])
774
+ ),
775
+ },
776
+ },
777
+ generate(printer, node) {
778
+ // TODO: print leading comments
779
+ for (let i = 0; i < node.children.length; i++) {
780
+ printer.print(node.children[i], node);
781
+ if (i !== node.children.length - 1) {
782
+ printer.newline();
783
+ }
784
+ }
785
+ },
786
+ });
787
+
788
+ //-------------------------------------------------------------------------------
789
+ // Formatting
790
+ //-------------------------------------------------------------------------------
791
+ const Newline = defineType('Newline', {
792
+ generate(printer) {
793
+ printer.newline();
794
+ },
795
+ });
796
+
797
+ module.exports = {
798
+ Assignment,
799
+ AssignmentPattern,
800
+ AtContent,
801
+ AtReturn,
802
+ AtRule,
803
+ BlockStatement,
804
+ CallExpression,
805
+ Comment,
806
+ Declaration,
807
+ IfStatement,
808
+ Identifier,
809
+ LogicalExpression,
810
+ RestPattern,
811
+ Rule,
812
+ SassBoolean,
813
+ SassColor,
814
+ SassForward,
815
+ SassFunction,
816
+ SassFunctionCall,
817
+ SassImport,
818
+ SassNumber,
819
+ SassString,
820
+ SassList,
821
+ SassMap,
822
+ SassMapProperty,
823
+ SassModule,
824
+ SassValue,
825
+ SassMixin,
826
+ SassMixinCall,
827
+ StyleSheet,
828
+
829
+ // Formatting
830
+ Newline,
831
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Copyright OctaviaFlow
3
+ * Author: Vishal Kumar
4
+ * Created: 11/November/2025
5
+ *
6
+ * This source code is licensed under the Apache-2.0 license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+
10
+
11
+ 'use strict';
12
+
13
+ const definitions = require('./definitions');
14
+
15
+ const types = Object.keys(definitions).reduce((acc, key) => {
16
+ const { builder, type } = definitions[key];
17
+ return {
18
+ ...acc,
19
+ [type]: builder,
20
+ };
21
+ }, {});
22
+
23
+ module.exports = {
24
+ definitions,
25
+ types,
26
+ };
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Copyright OctaviaFlow
3
+ * Author: Vishal Kumar
4
+ * Created: 11/November/2025
5
+ *
6
+ * This source code is licensed under the Apache-2.0 license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+
10
+
11
+ 'use strict';
12
+
13
+ function defineType(type, { fields = {}, generate } = {}) {
14
+ const keys = Object.keys(fields);
15
+
16
+ if (typeof generate !== 'function') {
17
+ throw new Error(`Expected a \`generate\` method for type \`${type}\``);
18
+ }
19
+
20
+ /**
21
+ * Support both object builder pattern and variadic arguments pattern. This
22
+ * allows type builders to be called using `t.Identifer({ name: 'value' })`
23
+ * alongside `t.Identifier('value')`.
24
+ *
25
+ * In some cases, the former syntax is preferred while in other situations
26
+ * (particularly for primitive values) we would rather use the latter pattern.
27
+ */
28
+ function builder(...args) {
29
+ let input = args;
30
+
31
+ // If we are given a builder object, let's use that instead. The logic for
32
+ // this can get tricky, so we are looking to see if we are given one
33
+ // argument that is an object without a `type` annotation. This should be a
34
+ // decent heuristic to toggle between when someone is using an object
35
+ // builder versus passing in multiple arguments (or one argument in the case
36
+ // of types with one field)
37
+ if (
38
+ args.length === 1 &&
39
+ typeof args[0] === 'object' &&
40
+ !Array.isArray(args[0]) &&
41
+ args[0].type === undefined
42
+ ) {
43
+ input = args[0];
44
+ }
45
+
46
+ const node = {
47
+ type,
48
+ };
49
+
50
+ for (let i = 0; i < keys.length; i++) {
51
+ const key = keys[i];
52
+ const field = fields[key];
53
+ const value = Array.isArray(input) ? input[i] : input[key];
54
+
55
+ if (value !== undefined) {
56
+ field.validate(value, node);
57
+ node[key] = value;
58
+ continue;
59
+ }
60
+
61
+ if (!field.optional && value === undefined) {
62
+ throw new Error(
63
+ `Expected field '${key}' to be defined for type ${type}`
64
+ );
65
+ }
66
+ }
67
+
68
+ return node;
69
+ }
70
+
71
+ return {
72
+ builder,
73
+ generate,
74
+ type,
75
+ };
76
+ }
77
+
78
+ module.exports = {
79
+ defineType,
80
+ };