@gesslar/sassy 0.20.2 → 0.21.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.
package/src/Data.js CHANGED
@@ -59,7 +59,10 @@ export default class Data {
59
59
  *
60
60
  * @type {string[]}
61
61
  */
62
- static dataTypes = Object.freeze([...Data.primitives, ...Data.constructors.map(c => c.toLowerCase())])
62
+ static dataTypes = Object.freeze([
63
+ ...Data.primitives,
64
+ ...Data.constructors.map(c => c.toLowerCase())
65
+ ])
63
66
 
64
67
  /**
65
68
  * Array of type names that can be checked for emptiness.
@@ -155,12 +158,13 @@ export default class Data {
155
158
  *
156
159
  * @param {Array} arr - The array to pad.
157
160
  * @param {number} length - The length to pad the array to.
158
- * @param {any} value - The value to pad the array with.
161
+ * @param {unknown} value - The value to pad the array with.
159
162
  * @param {number} position - The position to pad the array at.
160
163
  * @returns {Array} The padded array.
161
164
  */
162
165
  static arrayPad(arr, length, value, position = 0) {
163
166
  const diff = length - arr.length
167
+
164
168
  if(diff <= 0)
165
169
  return arr
166
170
 
@@ -199,8 +203,8 @@ export default class Data {
199
203
  /**
200
204
  * Allocates an object from a source array and a spec array or function.
201
205
  *
202
- * @param {any} source The source array
203
- * @param {any|Function} spec The spec array or function
206
+ * @param {unknown} source The source array
207
+ * @param {Array|function(unknown): unknown} spec The spec array or function
204
208
  * @returns {Promise<object>} The allocated object
205
209
  */
206
210
  static async allocateObject(source, spec) {
@@ -250,7 +254,7 @@ export default class Data {
250
254
  * Maps an object using a transformer function
251
255
  *
252
256
  * @param {object} original The original object
253
- * @param {Function} transformer The transformer function
257
+ * @param {function(unknown): unknown} transformer The transformer function
254
258
  * @param {boolean} mutate Whether to mutate the original object
255
259
  * @returns {Promise<object>} The mapped object
256
260
  */
@@ -294,13 +298,15 @@ export default class Data {
294
298
  /**
295
299
  * Checks if a value is of a specified type
296
300
  *
297
- * @param {any} value The value to check
301
+ * @param {unknown} value The value to check
298
302
  * @param {string|TypeSpec} type The type to check for
299
303
  * @param {object} options Additional options for checking
300
304
  * @returns {boolean} Whether the value is of the specified type
301
305
  */
302
306
  static isType(value, type, options = {}) {
303
- const typeSpec = type instanceof TypeSpec ? type : Data.newTypeSpec(type, options)
307
+ const typeSpec = type instanceof TypeSpec
308
+ ? type
309
+ : Data.newTypeSpec(type, options)
304
310
 
305
311
  return typeSpec.match(value, options)
306
312
  }
@@ -320,7 +326,7 @@ export default class Data {
320
326
  * function does not parse the type string, and only checks for primitive
321
327
  * or constructor types.
322
328
  *
323
- * @param {any} value - The value to check
329
+ * @param {unknown} value - The value to check
324
330
  * @param {string} type - The type to check for
325
331
  * @returns {boolean} Whether the value is of the specified type
326
332
  */
@@ -359,7 +365,7 @@ export default class Data {
359
365
  /**
360
366
  * Returns the type of a value, whether it be a primitive, object, or function.
361
367
  *
362
- * @param {any} value - The value to check
368
+ * @param {unknown} value - The value to check
363
369
  * @returns {string} The type of the value
364
370
  */
365
371
  static typeOf(value) {
@@ -369,7 +375,7 @@ export default class Data {
369
375
  /**
370
376
  * Checks a value is undefined or null.
371
377
  *
372
- * @param {any} value The value to check
378
+ * @param {unknown} value The value to check
373
379
  * @returns {boolean} Whether the value is undefined or null
374
380
  */
375
381
  static isNothing(value) {
@@ -380,7 +386,7 @@ export default class Data {
380
386
  * Checks if a value is empty. This function is used to check if an object,
381
387
  * array, or string is empty. Null and undefined values are considered empty.
382
388
  *
383
- * @param {any} value The value to check
389
+ * @param {unknown} value The value to check
384
390
  * @param {boolean} checkForNothing Whether to check for null or undefined
385
391
  * values
386
392
  * @returns {boolean} Whether the value is empty
@@ -423,7 +429,7 @@ export default class Data {
423
429
  const value = obj[name]
424
430
 
425
431
  // Recursively freeze nested objects
426
- if(value && typeof value === "object")
432
+ if(typeof value === "object" && value !== null)
427
433
  Data.deepFreezeObject(value)
428
434
  }
429
435
 
@@ -442,8 +448,10 @@ export default class Data {
442
448
  static assureObjectPath(obj, keys) {
443
449
  let current = obj // a moving reference to internal objects within obj
444
450
  const len = keys.length
451
+
445
452
  for(let i = 0; i < len; i++) {
446
453
  const elem = keys[i]
454
+
447
455
  if(!current[elem])
448
456
  current[elem] = {}
449
457
 
@@ -459,8 +467,8 @@ export default class Data {
459
467
  * the structure if it does not exist.
460
468
  *
461
469
  * @param {object} obj - The target object to set the value in
462
- * @param {string[]} keys - Array of keys representing the path to the target property
463
- * @param {*} value - The value to set at the target location
470
+ * @param {Array<string>} keys - Array of keys representing the path to the target property
471
+ * @param {unknown} value - The value to set at the target location
464
472
  */
465
473
  static setNestedValue(obj, keys, value) {
466
474
  const nested = Data.assureObjectPath(obj, keys.slice(0, -1))
@@ -475,7 +483,8 @@ export default class Data {
475
483
  * @returns {object} The merged object
476
484
  */
477
485
  static mergeObject(...sources) {
478
- const isObject = obj => obj && typeof obj === "object" && !Array.isArray(obj)
486
+ const isObject = obj => typeof obj === "object" && obj !== null && !Array.isArray(obj)
487
+
479
488
  return sources.reduce((acc, obj) => {
480
489
  if(!isObject(obj))
481
490
  return acc
@@ -512,11 +521,12 @@ export default class Data {
512
521
  * Applies the predicate to all items in parallel and returns filtered results.
513
522
  *
514
523
  * @param {Array} arr - The array to filter
515
- * @param {Function} predicate - Async predicate function that returns a promise resolving to boolean
524
+ * @param {function(unknown): Promise<boolean>} predicate - Async predicate function that returns a promise resolving to boolean
516
525
  * @returns {Promise<Array>} Promise resolving to the filtered array
517
526
  */
518
527
  static async asyncFilter(arr, predicate) {
519
528
  const results = await Promise.all(arr.map(predicate))
529
+
520
530
  return arr.filter((_, index) => results[index])
521
531
  }
522
532
 
package/src/Evaluator.js CHANGED
@@ -56,6 +56,37 @@ export default class Evaluator {
56
56
  */
57
57
  static func = /(?<captured>(?<func>\w+)\((?<args>[^()]+)\))/
58
58
 
59
+ /**
60
+ * Extracts a variable name from a string containing variable syntax.
61
+ * Supports $(var), $var, and ${var} patterns.
62
+ *
63
+ * @param {string} [str] - String that may contain a variable reference
64
+ * @returns {string|null} The variable name or null if none found
65
+ */
66
+ static extractVariableName(str="") {
67
+ const {none, parens, braces} = Evaluator.sub.exec(str)?.groups ?? {}
68
+
69
+ return none || parens || braces || null
70
+ }
71
+
72
+ /**
73
+ * Extracts function name and arguments from a string containing function syntax.
74
+ * Supports functionName(args) patterns.
75
+ *
76
+ * @param {string} [str] - String that may contain a function call
77
+ * @returns {{func:string, args:string}|null} Object with {func, args} or null if none found
78
+ */
79
+ static extractFunctionCall(str="") {
80
+ const match = Evaluator.func.exec(str)
81
+
82
+ if(!match?.groups)
83
+ return null
84
+
85
+ const {func, args} = match.groups
86
+
87
+ return {func, args}
88
+ }
89
+
59
90
  #pool = new ThemePool()
60
91
  get pool() {
61
92
  return this.#pool
@@ -80,19 +111,34 @@ export default class Evaluator {
80
111
  * - Input array is mutated in-place (`value` fields change).
81
112
  * - No return value. Evident by the absence of a return statement.
82
113
  *
83
- * @param {Array<{flatPath:string,value:any}>} decomposed - Variable entries to resolve.
114
+ * @param {Array<{flatPath:string,value:unknown}>} decomposed - Variable entries to resolve.
115
+ * @example
116
+ * // Example decomposed input with variables and theme references
117
+ * const evaluator = new Evaluator();
118
+ * const decomposed = [
119
+ * { flatPath: 'vars.primary', value: '#3366cc' },
120
+ * { flatPath: 'theme.colors.background', value: '$(vars.primary)' },
121
+ * { flatPath: 'theme.colors.accent', value: 'lighten($(vars.primary), 20)' }
122
+ * ];
123
+ * evaluator.evaluate(decomposed);
124
+ * // After evaluation, values are resolved:
125
+ * // decomposed[1].value === '#3366cc'
126
+ * // decomposed[2].value === '#5588dd' (lightened color)
84
127
  */
85
128
  evaluate(decomposed) {
86
129
  let it = 0
130
+
87
131
  do {
88
132
  decomposed.forEach(item => {
89
133
  const trail = new Array()
90
134
 
91
135
  if(typeof item.value === "string") {
92
136
  const raw = item.value
137
+
93
138
  item.value = this.#evaluateValue(trail, item.flatPath, raw)
94
139
  // Keep lookup in sync with latest resolved value for chained deps.
95
140
  const token = this.#pool.findToken(item.flatPath)
141
+
96
142
  this.#pool.resolve(item.flatPath, item.value)
97
143
  this.#pool.rawResolve(raw, item.value)
98
144
 
@@ -137,7 +183,7 @@ export default class Evaluator {
137
183
  * @param {string} parentTokenKeyString - Key string for parent token.
138
184
  * @param {string} value - Raw tokenised string to resolve.
139
185
  * @returns {string?} Fully resolved string.
140
- * @throws If we've reached maximum iterations.
186
+ * @throws {Sass} If we've reached maximum iterations.
141
187
  */
142
188
  #evaluateValue(trail, parentTokenKeyString, value) {
143
189
  let it = 0
@@ -194,6 +240,7 @@ export default class Evaluator {
194
240
 
195
241
  // Check if this is a color function (like oklch, rgb, hsl, etc.)
196
242
  const parsedColor = parse(value)
243
+
197
244
  if(parsedColor) {
198
245
  token.setParsedColor(parsedColor)
199
246
  }
@@ -225,8 +272,8 @@ export default class Evaluator {
225
272
  * @returns {ThemeToken|null} The resolved token or null.
226
273
  */
227
274
  #resolveVariable(value) {
228
- const {captured,none,parens,braces} = Evaluator.sub.exec(value).groups
229
- const work = none ?? parens ?? braces
275
+ const {captured} = Evaluator.sub.exec(value).groups
276
+ const work = Evaluator.extractVariableName(value)
230
277
  const existing = this.#pool.findToken(work)
231
278
 
232
279
  if(!existing)
@@ -249,13 +296,19 @@ export default class Evaluator {
249
296
  * @returns {ThemeToken|null} The resolved token or null.
250
297
  */
251
298
  #resolveFunction(value) {
252
- const {captured,func,args} = Evaluator.func.exec(value).groups
299
+ const {captured} = Evaluator.func.exec(value).groups
300
+ const result = Evaluator.extractFunctionCall(value)
301
+
302
+ if(!result)
303
+ return null
304
+
305
+ const {func, args} = result
253
306
  const split = args?.split(",").map(a => a.trim()) ?? []
254
307
 
255
308
  // Look up source tokens for arguments to preserve color space
256
309
  const sourceTokens = split.map(arg => {
257
310
  return this.#pool.findToken(arg) ||
258
- this.#pool.getTokens?.get?.(arg) ||
311
+ this.#pool.getTokens()?.get?.(arg) ||
259
312
  null
260
313
  })
261
314
 
@@ -336,7 +389,7 @@ export default class Evaluator {
336
389
  * Predicate: does this item's value still contain variable or function tokens?
337
390
  *
338
391
  * @private
339
- * @param {{value:any}} item - Entry to test.
392
+ * @param {{value:unknown}} item - Entry to test.
340
393
  * @returns {boolean} True if token patterns present.
341
394
  */
342
395
  #tokenCheck(item) {
package/src/File.js CHANGED
@@ -39,6 +39,7 @@ export default class File {
39
39
  return url.pathToFileURL(pathName).href
40
40
  } catch(e) {
41
41
  void e // stfu linter
42
+
42
43
  return pathName
43
44
  }
44
45
  }
@@ -52,6 +53,7 @@ export default class File {
52
53
  static async canReadFile(file) {
53
54
  try {
54
55
  await fs.access(file.path, fs.constants.R_OK)
56
+
55
57
  return true
56
58
  } catch(_) {
57
59
  return false
@@ -67,6 +69,7 @@ export default class File {
67
69
  static async canWriteFile(file) {
68
70
  try {
69
71
  await fs.access(file.path, fs.constants.W_OK)
72
+
70
73
  return true
71
74
  } catch(_error) {
72
75
  return false
@@ -82,6 +85,7 @@ export default class File {
82
85
  static async fileExists(file) {
83
86
  try {
84
87
  await fs.access(file.path, fs.constants.R_OK)
88
+
85
89
  return true
86
90
  } catch(_) {
87
91
  return false
@@ -97,6 +101,7 @@ export default class File {
97
101
  static async fileSize(file) {
98
102
  try {
99
103
  const stat = await fs.stat(file.path)
104
+
100
105
  return stat.size
101
106
  } catch(_) {
102
107
  return null
@@ -113,6 +118,7 @@ export default class File {
113
118
  static async fileModified(file) {
114
119
  try {
115
120
  const stat = await fs.stat(file.path)
121
+
116
122
  return stat.mtime
117
123
  } catch(_) {
118
124
  return null
@@ -179,8 +185,13 @@ export default class File {
179
185
  */
180
186
  static async getFiles(glob) {
181
187
  Valid.assert(
182
- ((typeof glob === "string" && glob.length > 0) ||
183
- (Array.isArray(glob) && Data.uniformStringArray(glob) && glob.length > 0)),
188
+ (
189
+ (typeof glob === "string" && glob.length > 0) ||
190
+ (
191
+ Array.isArray(glob) && Data.uniformStringArray(glob) &&
192
+ glob.length > 0
193
+ )
194
+ ),
184
195
  "glob must be a non-empty string or array of strings.",
185
196
  1
186
197
  )
@@ -225,6 +236,7 @@ export default class File {
225
236
  found.map(async dirent => {
226
237
  const fullPath = path.join(directory, dirent.name)
227
238
  const stat = await fs.stat(fullPath)
239
+
228
240
  return {dirent, stat, fullPath}
229
241
  }),
230
242
  )