@gesslar/sassy 0.19.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/README.md +605 -0
- package/UNLICENSE.txt +24 -0
- package/package.json +60 -0
- package/src/BuildCommand.js +183 -0
- package/src/Cache.js +73 -0
- package/src/Colour.js +414 -0
- package/src/Command.js +212 -0
- package/src/Compiler.js +310 -0
- package/src/Data.js +545 -0
- package/src/DirectoryObject.js +188 -0
- package/src/Evaluator.js +348 -0
- package/src/File.js +334 -0
- package/src/FileObject.js +226 -0
- package/src/LintCommand.js +498 -0
- package/src/ResolveCommand.js +433 -0
- package/src/Sass.js +165 -0
- package/src/Session.js +360 -0
- package/src/Term.js +175 -0
- package/src/Theme.js +289 -0
- package/src/ThemePool.js +139 -0
- package/src/ThemeToken.js +280 -0
- package/src/Type.js +206 -0
- package/src/Util.js +132 -0
- package/src/Valid.js +50 -0
- package/src/cli.js +155 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file DirectoryObject.js
|
|
3
|
+
* @description Class representing a directory and its metadata, including path
|
|
4
|
+
* resolution and existence checks.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from "node:path"
|
|
8
|
+
import util from "node:util"
|
|
9
|
+
|
|
10
|
+
import File from "./File.js"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* DirectoryObject encapsulates metadata and operations for a directory,
|
|
14
|
+
* including path resolution and existence checks.
|
|
15
|
+
*
|
|
16
|
+
* @property {string} supplied - The supplied directory
|
|
17
|
+
* @property {string} path - The resolved path
|
|
18
|
+
* @property {string} uri - The directory URI
|
|
19
|
+
* @property {string} name - The directory name
|
|
20
|
+
* @property {string} module - The directory name without extension
|
|
21
|
+
* @property {string} extension - The directory extension (usually empty)
|
|
22
|
+
* @property {boolean} isFile - Always false
|
|
23
|
+
* @property {boolean} isDirectory - Always true
|
|
24
|
+
* @property {Promise<boolean>} exists - Whether the directory exists (async)
|
|
25
|
+
*/
|
|
26
|
+
export default class DirectoryObject {
|
|
27
|
+
/**
|
|
28
|
+
* @type {object}
|
|
29
|
+
* @private
|
|
30
|
+
* @property {string|null} supplied - User-supplied path
|
|
31
|
+
* @property {string|null} path - The absolute file path
|
|
32
|
+
* @property {string|null} uri - The file URI
|
|
33
|
+
* @property {string|null} name - The file name
|
|
34
|
+
* @property {string|null} module - The file name without extension
|
|
35
|
+
* @property {string|null} extension - The file extension
|
|
36
|
+
* @property {boolean} isFile - Always false
|
|
37
|
+
* @property {boolean} isDirectory - Always true
|
|
38
|
+
*/
|
|
39
|
+
#meta = Object.seal({
|
|
40
|
+
supplied: null,
|
|
41
|
+
path: null,
|
|
42
|
+
uri: null,
|
|
43
|
+
name: null,
|
|
44
|
+
module: null,
|
|
45
|
+
extension: null,
|
|
46
|
+
isFile: false,
|
|
47
|
+
isDirectory: true,
|
|
48
|
+
directory: null,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Constructs a DirectoryObject instance.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} directory - The directory path
|
|
55
|
+
*/
|
|
56
|
+
constructor(directory) {
|
|
57
|
+
const fixedDir = File.fixSlashes(directory ?? ".")
|
|
58
|
+
const {base,ext} = File.deconstructFilenameToParts(fixedDir)
|
|
59
|
+
const fileUri = File.pathToUri(fixedDir)
|
|
60
|
+
const filePath = File.uriToPath(fileUri)
|
|
61
|
+
|
|
62
|
+
this.#meta.supplied = fixedDir
|
|
63
|
+
this.#meta.path = filePath
|
|
64
|
+
this.#meta.uri = fileUri
|
|
65
|
+
this.#meta.name = base
|
|
66
|
+
this.#meta.extension = ext
|
|
67
|
+
this.#meta.module = path.basename(this.supplied, this.extension)
|
|
68
|
+
|
|
69
|
+
Object.freeze(this.#meta)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Returns a string representation of the DirectoryObject.
|
|
74
|
+
*
|
|
75
|
+
* @returns {string} string representation of the DirectoryObject
|
|
76
|
+
*/
|
|
77
|
+
toString() {
|
|
78
|
+
return `[DirectoryObject: ${this.path}]`
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Returns a JSON representation of the DirectoryObject.
|
|
83
|
+
*
|
|
84
|
+
* @returns {object} JSON representation of the DirectoryObject
|
|
85
|
+
*/
|
|
86
|
+
toJSON() {
|
|
87
|
+
return {
|
|
88
|
+
supplied: this.supplied,
|
|
89
|
+
path: this.path,
|
|
90
|
+
uri: this.uri,
|
|
91
|
+
name: this.name,
|
|
92
|
+
module: this.module,
|
|
93
|
+
extension: this.extension,
|
|
94
|
+
isFile: this.isFile,
|
|
95
|
+
isDirectory: this.isDirectory
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Custom inspect method for Node.js console.
|
|
101
|
+
*
|
|
102
|
+
* @returns {object} JSON representation of this object.
|
|
103
|
+
*/
|
|
104
|
+
[util.inspect.custom]() {
|
|
105
|
+
return this.toJSON()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Checks if the directory exists (async).
|
|
110
|
+
*
|
|
111
|
+
* @returns {Promise<boolean>} - A Promise that resolves to true or false
|
|
112
|
+
*/
|
|
113
|
+
get exists() {
|
|
114
|
+
return File.directoryExists(this)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Return the path as passed to the constructor.
|
|
119
|
+
*
|
|
120
|
+
* @returns {string} The directory path
|
|
121
|
+
*/
|
|
122
|
+
get supplied() {
|
|
123
|
+
return this.#meta.supplied
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Return the resolved path
|
|
128
|
+
*
|
|
129
|
+
* @returns {string} The directory path
|
|
130
|
+
*/
|
|
131
|
+
get path() {
|
|
132
|
+
return this.#meta.path
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Returns the URI of the current directory.
|
|
137
|
+
*
|
|
138
|
+
* @returns {string} The directory URI
|
|
139
|
+
*/
|
|
140
|
+
get uri() {
|
|
141
|
+
return this.#meta.uri
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Returns the directory name with extension (if any) without the path.
|
|
146
|
+
*
|
|
147
|
+
* @returns {string} The directory name
|
|
148
|
+
*/
|
|
149
|
+
get name() {
|
|
150
|
+
return this.#meta.name
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Returns the directory name without the path or extension.
|
|
155
|
+
*
|
|
156
|
+
* @returns {string} The directory name without extension
|
|
157
|
+
*/
|
|
158
|
+
get module() {
|
|
159
|
+
return this.#meta.module
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Returns the directory extension. Will be an empty string if unavailable.
|
|
164
|
+
*
|
|
165
|
+
* @returns {string} The directory extension
|
|
166
|
+
*/
|
|
167
|
+
get extension() {
|
|
168
|
+
return this.#meta.extension
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Returns false. Because this is a directory.
|
|
173
|
+
*
|
|
174
|
+
* @returns {boolean} Always false
|
|
175
|
+
*/
|
|
176
|
+
get isFile() {
|
|
177
|
+
return this.#meta.isFile
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* We're a directory!
|
|
182
|
+
*
|
|
183
|
+
* @returns {boolean} Always true
|
|
184
|
+
*/
|
|
185
|
+
get isDirectory() {
|
|
186
|
+
return this.#meta.isDirectory
|
|
187
|
+
}
|
|
188
|
+
}
|
package/src/Evaluator.js
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Evaluator.js
|
|
3
|
+
*
|
|
4
|
+
* Defines the Evaluator class, responsible for variable and token resolution
|
|
5
|
+
* during theme compilation.
|
|
6
|
+
*
|
|
7
|
+
* Handles recursive substitution of variable references and colour function
|
|
8
|
+
* calls within theme configuration objects.
|
|
9
|
+
*
|
|
10
|
+
* Ensures deterministic scoping and supports extension for new colour
|
|
11
|
+
* functions.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {parse} from "culori"
|
|
15
|
+
|
|
16
|
+
import Sass from "./Sass.js"
|
|
17
|
+
import Colour from "./Colour.js"
|
|
18
|
+
import ThemePool from "./ThemePool.js"
|
|
19
|
+
import ThemeToken from "./ThemeToken.js"
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Evaluator class for resolving variables and colour tokens in theme objects.
|
|
23
|
+
* Handles recursive substitution of token references in arrays of objects
|
|
24
|
+
* with support for colour manipulation functions.
|
|
25
|
+
*/
|
|
26
|
+
export default class Evaluator {
|
|
27
|
+
/**
|
|
28
|
+
* Maximum number of passes allowed while resolving tokens within a scope.
|
|
29
|
+
* Prevents infinite recursion in the event of cyclical or self-referential
|
|
30
|
+
* variable definitions.
|
|
31
|
+
*
|
|
32
|
+
* @private
|
|
33
|
+
* @type {number}
|
|
34
|
+
*/
|
|
35
|
+
#maxIterations = 10
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Regular expression used to locate variable substitution tokens. Supports:
|
|
39
|
+
* - POSIX-ish: $(variable.path)
|
|
40
|
+
* - Legacy: $variable.path
|
|
41
|
+
* - Braced: ${variable.path}
|
|
42
|
+
*
|
|
43
|
+
* Capturing groups allow extraction of the inner path variant irrespective
|
|
44
|
+
* of wrapping style. The pattern captures (entireMatch, posix, legacy,
|
|
45
|
+
* braced).
|
|
46
|
+
*
|
|
47
|
+
* @type {RegExp}
|
|
48
|
+
*/
|
|
49
|
+
static sub = /(?<captured>\$\((?<parens>[^()]+)\)|\$(?<none>[\w]+(?:\.[\w]+)*)|\$\{(?<braces>[^()]+)\})/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Regular expression for matching colour / transformation function calls
|
|
53
|
+
* within token strings, e.g. `darken($(std.accent), 10)`.
|
|
54
|
+
*
|
|
55
|
+
* @type {RegExp}
|
|
56
|
+
*/
|
|
57
|
+
static func = /(?<captured>(?<func>\w+)\((?<args>[^()]+)\))/
|
|
58
|
+
|
|
59
|
+
#pool = new ThemePool()
|
|
60
|
+
get pool() {
|
|
61
|
+
return this.#pool
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Resolve variables and theme token entries in two distinct passes to ensure
|
|
66
|
+
* deterministic scoping and to prevent partially-resolved values from
|
|
67
|
+
* leaking between stages:
|
|
68
|
+
*
|
|
69
|
+
* 1. Variable pass: each variable is resolved only with access to the
|
|
70
|
+
* variable set itself (no theme values yet). This ensures variables are
|
|
71
|
+
* self-contained building blocks.
|
|
72
|
+
* 2. Theme pass: theme entries are then resolved against the union of the
|
|
73
|
+
* fully-resolved variables plus (progressively) the theme entries. This
|
|
74
|
+
* allows theme keys to reference variables and other theme keys.
|
|
75
|
+
*
|
|
76
|
+
* Implementation details:
|
|
77
|
+
* - The internal lookup map persists for the lifetime of this instance; new
|
|
78
|
+
* entries overwrite prior values (last write wins) so previously resolved
|
|
79
|
+
* data can seed later evaluations without a rebuild.
|
|
80
|
+
* - Input array is mutated in-place (`value` fields change).
|
|
81
|
+
* - No return value. Evident by the absence of a return statement.
|
|
82
|
+
*
|
|
83
|
+
* @param {Array<{flatPath:string,value:any}>} decomposed - Variable entries to resolve.
|
|
84
|
+
*/
|
|
85
|
+
evaluate(decomposed) {
|
|
86
|
+
let it = 0
|
|
87
|
+
do {
|
|
88
|
+
decomposed.forEach(item => {
|
|
89
|
+
const trail = new Array()
|
|
90
|
+
|
|
91
|
+
if(typeof item.value === "string") {
|
|
92
|
+
const raw = item.value
|
|
93
|
+
item.value = this.#evaluateValue(trail, item.flatPath, raw)
|
|
94
|
+
// Keep lookup in sync with latest resolved value for chained deps.
|
|
95
|
+
const token = this.#pool.findToken(item.flatPath)
|
|
96
|
+
this.#pool.resolve(item.flatPath, item.value)
|
|
97
|
+
this.#pool.rawResolve(raw, item.value)
|
|
98
|
+
|
|
99
|
+
if(token) {
|
|
100
|
+
token.setValue(item.value).addTrail(trail)
|
|
101
|
+
} else {
|
|
102
|
+
const newToken = new ThemeToken(item.flatPath)
|
|
103
|
+
.setRawValue(raw)
|
|
104
|
+
.setValue(item.value)
|
|
105
|
+
.setKind("input")
|
|
106
|
+
.addTrail(trail)
|
|
107
|
+
|
|
108
|
+
this.#pool.addToken(newToken)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
} while(
|
|
113
|
+
++it < this.#maxIterations &&
|
|
114
|
+
this.#hasUnresolvedTokens(decomposed)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if(it === this.#maxIterations) {
|
|
118
|
+
const unresolved = decomposed
|
|
119
|
+
.filter(this.#tokenCheck)
|
|
120
|
+
.map(token => token.flatPath)
|
|
121
|
+
|
|
122
|
+
throw Sass.new(
|
|
123
|
+
"Luuuucyyyy! We tried to resolve your tokens, but there were just "+
|
|
124
|
+
"too many! Suspect maybe some circular references are interfering "+
|
|
125
|
+
"with your bliss. These are the ones that remain unresolved: " +
|
|
126
|
+
unresolved.toString()
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Resolve a variable or function token inside a string value; else return
|
|
133
|
+
* the passed value.
|
|
134
|
+
*
|
|
135
|
+
* @private
|
|
136
|
+
* @param {Array<ThemeToken>} trail - Array to track resolution chain.
|
|
137
|
+
* @param {string} parentTokenKeyString - Key string for parent token.
|
|
138
|
+
* @param {string} value - Raw tokenised string to resolve.
|
|
139
|
+
* @returns {string?} Fully resolved string.
|
|
140
|
+
* @throws If we've reached maximum iterations.
|
|
141
|
+
*/
|
|
142
|
+
#evaluateValue(trail, parentTokenKeyString, value) {
|
|
143
|
+
let it = 0
|
|
144
|
+
|
|
145
|
+
do {
|
|
146
|
+
let resolved
|
|
147
|
+
|
|
148
|
+
if(Colour.isHex(value))
|
|
149
|
+
resolved = this.#resolveHex(value)
|
|
150
|
+
else if(Evaluator.sub.test(value))
|
|
151
|
+
resolved = this.#resolveVariable(value)
|
|
152
|
+
else if(Evaluator.func.test(value))
|
|
153
|
+
resolved = this.#resolveFunction(value)
|
|
154
|
+
else
|
|
155
|
+
resolved = this.#resolveLiteral(value)
|
|
156
|
+
|
|
157
|
+
if(!resolved || resolved.getValue() === value)
|
|
158
|
+
return value
|
|
159
|
+
|
|
160
|
+
// Otherwise keep processing the new value
|
|
161
|
+
this.#pool.addToken(resolved).setParentTokenKey(parentTokenKeyString)
|
|
162
|
+
trail.push(resolved)
|
|
163
|
+
value = resolved.getValue()
|
|
164
|
+
} while(++it < this.#maxIterations)
|
|
165
|
+
|
|
166
|
+
if(it === this.#maxIterations) {
|
|
167
|
+
throw Sass.new("HMMMMM! It looks like you might have some " +
|
|
168
|
+
"circular resolution happening. We tried to fix it up, but this " +
|
|
169
|
+
"doesn't seem to be working out. Trying to resolve: " +
|
|
170
|
+
`${parentTokenKeyString}, we got as far as ${value}, before we ` +
|
|
171
|
+
"called an end to this interminable game of Duck-Duck-Goose.")
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return // it'll never reach here, but the linter got mad so i gave it a tit
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Resolve a literal value to a ThemeToken.
|
|
179
|
+
*
|
|
180
|
+
* @private
|
|
181
|
+
* @param {string} value - The literal value.
|
|
182
|
+
* @returns {ThemeToken} The resolved token.
|
|
183
|
+
*/
|
|
184
|
+
#resolveLiteral(value) {
|
|
185
|
+
const existing = this.#pool.findToken(value)
|
|
186
|
+
|
|
187
|
+
if(existing)
|
|
188
|
+
return existing
|
|
189
|
+
|
|
190
|
+
const token = new ThemeToken(value)
|
|
191
|
+
.setKind("literal")
|
|
192
|
+
.setRawValue(value)
|
|
193
|
+
.setValue(value)
|
|
194
|
+
|
|
195
|
+
// Check if this is a color function (like oklch, rgb, hsl, etc.)
|
|
196
|
+
const parsedColor = parse(value)
|
|
197
|
+
if(parsedColor) {
|
|
198
|
+
token.setParsedColor(parsedColor)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return token
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Resolve a hex colour value to a ThemeToken.
|
|
206
|
+
*
|
|
207
|
+
* @private
|
|
208
|
+
* @param {string} value - The hex colour value.
|
|
209
|
+
* @returns {ThemeToken} The resolved token.
|
|
210
|
+
*/
|
|
211
|
+
#resolveHex(value) {
|
|
212
|
+
const hex = Colour.normaliseHex(value)
|
|
213
|
+
|
|
214
|
+
return new ThemeToken(value)
|
|
215
|
+
.setKind("hex")
|
|
216
|
+
.setRawValue(value)
|
|
217
|
+
.setValue(hex)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Resolve a variable token to its value.
|
|
222
|
+
*
|
|
223
|
+
* @private
|
|
224
|
+
* @param {string} value - The variable token string.
|
|
225
|
+
* @returns {ThemeToken|null} The resolved token or null.
|
|
226
|
+
*/
|
|
227
|
+
#resolveVariable(value) {
|
|
228
|
+
const {captured,none,parens,braces} = Evaluator.sub.exec(value).groups
|
|
229
|
+
const work = none ?? parens ?? braces
|
|
230
|
+
const existing = this.#pool.findToken(work)
|
|
231
|
+
|
|
232
|
+
if(!existing)
|
|
233
|
+
return null
|
|
234
|
+
|
|
235
|
+
const resolved = value.replace(captured,existing.getValue())
|
|
236
|
+
|
|
237
|
+
return new ThemeToken(value)
|
|
238
|
+
.setKind("variable")
|
|
239
|
+
.setRawValue(captured)
|
|
240
|
+
.setValue(resolved)
|
|
241
|
+
.setDependency(existing)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Resolve a function token to its value.
|
|
246
|
+
*
|
|
247
|
+
* @private
|
|
248
|
+
* @param {string} value - The function token string.
|
|
249
|
+
* @returns {ThemeToken|null} The resolved token or null.
|
|
250
|
+
*/
|
|
251
|
+
#resolveFunction(value) {
|
|
252
|
+
const {captured,func,args} = Evaluator.func.exec(value).groups
|
|
253
|
+
const split = args?.split(",").map(a => a.trim()) ?? []
|
|
254
|
+
|
|
255
|
+
// Look up source tokens for arguments to preserve color space
|
|
256
|
+
const sourceTokens = split.map(arg => {
|
|
257
|
+
return this.#pool.findToken(arg) ||
|
|
258
|
+
this.#pool.getTokens?.get?.(arg) ||
|
|
259
|
+
null
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
const applied = this.#colourFunction(func, split, value, sourceTokens)
|
|
263
|
+
|
|
264
|
+
if(!applied)
|
|
265
|
+
return null
|
|
266
|
+
|
|
267
|
+
const resolved = value.replace(captured, applied)
|
|
268
|
+
|
|
269
|
+
return new ThemeToken(value)
|
|
270
|
+
.setKind("function")
|
|
271
|
+
.setRawValue(captured)
|
|
272
|
+
.setValue(resolved)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Execute a supported colour transformation helper.
|
|
277
|
+
*
|
|
278
|
+
* @private
|
|
279
|
+
* @param {string} func - Function name (lighten|darken|fade|alpha|mix|...)
|
|
280
|
+
* @param {Array<string>} args - Raw argument strings (numbers still as text).
|
|
281
|
+
* @param {string} raw - The raw input from the source file.
|
|
282
|
+
* @param {Array<ThemeToken>} sourceTokens - The tokens to apply to.
|
|
283
|
+
* @returns {object} Object with result and colorSpace info.
|
|
284
|
+
*/
|
|
285
|
+
#colourFunction(func, args, raw, sourceTokens = []) {
|
|
286
|
+
return (() => {
|
|
287
|
+
try {
|
|
288
|
+
const sourceToken = sourceTokens[0]
|
|
289
|
+
|
|
290
|
+
switch(func) {
|
|
291
|
+
case "lighten":
|
|
292
|
+
return sourceToken
|
|
293
|
+
? Colour.lightenOrDarkenWithToken(sourceToken, Number(args[1]))
|
|
294
|
+
: Colour.lightenOrDarken(args[0], Number(args[1]))
|
|
295
|
+
case "darken":
|
|
296
|
+
return sourceToken
|
|
297
|
+
? Colour.lightenOrDarkenWithToken(sourceToken, -Number(args[1]))
|
|
298
|
+
: Colour.lightenOrDarken(args[0], -Number(args[1]))
|
|
299
|
+
case "fade":
|
|
300
|
+
return Colour.addAlpha(args[0], -Number(args[1]))
|
|
301
|
+
case "solidify":
|
|
302
|
+
return Colour.addAlpha(args[0], Number(args[1]))
|
|
303
|
+
case "alpha":
|
|
304
|
+
return Colour.setAlpha(args[0], Number(args[1]))
|
|
305
|
+
case "invert":
|
|
306
|
+
return Colour.invert(args[0])
|
|
307
|
+
case "mix":
|
|
308
|
+
return Colour.mix(
|
|
309
|
+
args[0],
|
|
310
|
+
args[1],
|
|
311
|
+
args[2] ? Number(args[2]) : undefined
|
|
312
|
+
)
|
|
313
|
+
case "css":
|
|
314
|
+
return Colour.toHex(args.toString())
|
|
315
|
+
default:
|
|
316
|
+
return Colour.toHex(raw)
|
|
317
|
+
}
|
|
318
|
+
} catch(e) {
|
|
319
|
+
throw Sass.new(`Performing colour function ${raw}`, e)
|
|
320
|
+
}
|
|
321
|
+
})()
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Determine whether further resolution passes are required for a scope.
|
|
326
|
+
*
|
|
327
|
+
* @private
|
|
328
|
+
* @param {Array<object>} arr - Scope entries to inspect.
|
|
329
|
+
* @returns {boolean} True if any unresolved tokens remain.
|
|
330
|
+
*/
|
|
331
|
+
#hasUnresolvedTokens(arr) {
|
|
332
|
+
return arr.some(item => this.#tokenCheck(item))
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Predicate: does this item's value still contain variable or function tokens?
|
|
337
|
+
*
|
|
338
|
+
* @private
|
|
339
|
+
* @param {{value:any}} item - Entry to test.
|
|
340
|
+
* @returns {boolean} True if token patterns present.
|
|
341
|
+
*/
|
|
342
|
+
#tokenCheck(item) {
|
|
343
|
+
if(typeof item.value !== "string")
|
|
344
|
+
return false
|
|
345
|
+
|
|
346
|
+
return Evaluator.sub.test(item.value) || Evaluator.func.test(item.value)
|
|
347
|
+
}
|
|
348
|
+
}
|