@gesslar/sassy 0.20.0 → 0.21.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 +12 -8
- package/src/BuildCommand.js +8 -9
- package/src/Cache.js +1 -0
- package/src/Colour.js +8 -2
- package/src/Command.js +92 -32
- package/src/Compiler.js +62 -36
- package/src/Data.js +24 -14
- package/src/Evaluator.js +8 -4
- package/src/File.js +14 -2
- package/src/LintCommand.js +381 -103
- package/src/ResolveCommand.js +44 -27
- package/src/Sass.js +2 -1
- package/src/Session.js +211 -42
- package/src/Term.js +7 -7
- package/src/Theme.js +378 -53
- package/src/ThemePool.js +1 -1
- package/src/ThemeToken.js +1 -0
- package/src/Type.js +11 -10
- package/src/Util.js +2 -2
- package/src/Valid.js +1 -1
- package/src/cli.js +27 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gesslar/sassy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"displayName": "Sassy",
|
|
5
5
|
"description": "Make gorgeous themes that speak as boldly as you do.",
|
|
6
6
|
"publisher": "gesslar",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"lint": "eslint src/",
|
|
20
20
|
"submit": "npm publish --access public",
|
|
21
21
|
"examples": "node ./examples/validator.js",
|
|
22
|
-
"test": "node -p \"require('fs').readFileSync('TESTING.txt', 'utf8')\""
|
|
22
|
+
"test": "node -p \"require('fs').readFileSync('TESTING.txt', 'utf8')\"",
|
|
23
|
+
"update": "npx npm-check-updates -u && npm install"
|
|
23
24
|
},
|
|
24
25
|
"engines": {
|
|
25
26
|
"node": ">=20.0.0"
|
|
@@ -44,17 +45,20 @@
|
|
|
44
45
|
"@gesslar/colours": "^0.0.1",
|
|
45
46
|
"chokidar": "^4.0.3",
|
|
46
47
|
"color-support": "^1.1.3",
|
|
47
|
-
"commander": "^14.0.
|
|
48
|
+
"commander": "^14.0.1",
|
|
48
49
|
"culori": "^4.0.2",
|
|
49
50
|
"globby": "^14.1.0",
|
|
50
51
|
"json5": "^2.2.3",
|
|
51
52
|
"yaml": "^2.8.1"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
54
|
-
"@stylistic/eslint-plugin": "^5.
|
|
55
|
-
"@
|
|
56
|
-
"@typescript-eslint/
|
|
57
|
-
"eslint": "^
|
|
58
|
-
"
|
|
55
|
+
"@stylistic/eslint-plugin": "^5.3.1",
|
|
56
|
+
"@types/vscode": "^1.104.0",
|
|
57
|
+
"@typescript-eslint/eslint-plugin": "^8.44.0",
|
|
58
|
+
"@typescript-eslint/parser": "^8.44.0",
|
|
59
|
+
"esbuild": "^0.25.10",
|
|
60
|
+
"eslint": "^9.35.0",
|
|
61
|
+
"eslint-plugin-jsdoc": "^59.0.1",
|
|
62
|
+
"typescript": "^5.9.2"
|
|
59
63
|
}
|
|
60
64
|
}
|
package/src/BuildCommand.js
CHANGED
|
@@ -28,13 +28,13 @@ export default class BuildCommand extends Command {
|
|
|
28
28
|
constructor(base) {
|
|
29
29
|
super(base)
|
|
30
30
|
|
|
31
|
-
this.
|
|
32
|
-
this.
|
|
31
|
+
this.setCliCommand("build <file...>")
|
|
32
|
+
this.setCliOptions({
|
|
33
33
|
"watch": ["-w, --watch", "watch for changes"],
|
|
34
34
|
"output-dir": ["-o, --output-dir <dir>", "specify an output directory"],
|
|
35
35
|
"dry-run": ["-n, --dry-run", "print theme JSON to stdout; do not write files"],
|
|
36
36
|
"silent": ["-s, --silent", "silent mode. only print errors or dry-run"],
|
|
37
|
-
}
|
|
37
|
+
})
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -51,7 +51,7 @@ export default class BuildCommand extends Command {
|
|
|
51
51
|
* @throws {Error} When theme compilation fails
|
|
52
52
|
*/
|
|
53
53
|
async execute(fileNames, options) {
|
|
54
|
-
const
|
|
54
|
+
const cwd = this.getCwd()
|
|
55
55
|
|
|
56
56
|
if(options.watch) {
|
|
57
57
|
options.watch && this.#initialiseInputHandler()
|
|
@@ -69,7 +69,7 @@ export default class BuildCommand extends Command {
|
|
|
69
69
|
fileNames.map(async fileName => {
|
|
70
70
|
const fileObject = await this.resolveThemeFileName(fileName, cwd)
|
|
71
71
|
const theme = new Theme(fileObject, cwd, options)
|
|
72
|
-
|
|
72
|
+
.setCache(this.getCache())
|
|
73
73
|
|
|
74
74
|
return new Session(this, theme, options)
|
|
75
75
|
})
|
|
@@ -83,12 +83,11 @@ export default class BuildCommand extends Command {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
const sessions = sessionResults.map(result => result.value)
|
|
86
|
-
const firstRun = await Promise.allSettled(
|
|
87
|
-
|
|
88
|
-
)
|
|
86
|
+
const firstRun = await Promise.allSettled(sessions.map(
|
|
87
|
+
async session => await session.run()))
|
|
89
88
|
const rejected = firstRun.filter(reject => reject.status === "rejected")
|
|
90
|
-
if(rejected.length > 0) {
|
|
91
89
|
|
|
90
|
+
if(rejected.length > 0) {
|
|
92
91
|
rejected.forEach(reject => Term.error(reject.reason))
|
|
93
92
|
|
|
94
93
|
if(firstRun.length === rejected.length)
|
package/src/Cache.js
CHANGED
package/src/Colour.js
CHANGED
|
@@ -23,7 +23,6 @@ const _colourCache = new Map()
|
|
|
23
23
|
// Cache for mixed colours to avoid recomputation
|
|
24
24
|
const _mixCache = new Map()
|
|
25
25
|
|
|
26
|
-
|
|
27
26
|
/**
|
|
28
27
|
* Parses a colour string into a colour object with caching.
|
|
29
28
|
*
|
|
@@ -58,10 +57,12 @@ const asColour = s => {
|
|
|
58
57
|
throw Sass.new("asColour(): received null/undefined")
|
|
59
58
|
|
|
60
59
|
const k = String(s).trim()
|
|
60
|
+
|
|
61
61
|
if(!k)
|
|
62
62
|
throw Sass.new("asColour(): received empty string")
|
|
63
63
|
|
|
64
64
|
let v = _colourCache.get(k)
|
|
65
|
+
|
|
65
66
|
if(!v) {
|
|
66
67
|
v = parse(k) // returns undefined if invalid
|
|
67
68
|
|
|
@@ -140,9 +141,11 @@ export default class Colour {
|
|
|
140
141
|
|
|
141
142
|
// Use multiplicative scaling for more natural results
|
|
142
143
|
const factor = 1 + (amount / 100)
|
|
144
|
+
|
|
143
145
|
oklchColor.l = clamp(oklchColor.l * factor, 0, 1)
|
|
144
146
|
|
|
145
147
|
const result = `${formatHex(oklchColor)}${extracted.alpha?.hex??""}`.toLowerCase()
|
|
148
|
+
|
|
146
149
|
return result
|
|
147
150
|
}
|
|
148
151
|
|
|
@@ -177,12 +180,12 @@ export default class Colour {
|
|
|
177
180
|
|
|
178
181
|
// Use multiplicative scaling
|
|
179
182
|
const factor = 1 + (amount / 100)
|
|
183
|
+
|
|
180
184
|
oklchColor.l = clamp(oklchColor.l * factor, 0, 1)
|
|
181
185
|
|
|
182
186
|
return formatHex(oklchColor).toLowerCase()
|
|
183
187
|
}
|
|
184
188
|
|
|
185
|
-
|
|
186
189
|
/**
|
|
187
190
|
* Inverts a hex colour by flipping its lightness value.
|
|
188
191
|
* Preserves hue and saturation while inverting the lightness component.
|
|
@@ -193,6 +196,7 @@ export default class Colour {
|
|
|
193
196
|
static invert(hex) {
|
|
194
197
|
const extracted = Colour.parseHexColour(hex)
|
|
195
198
|
const hslColour = hsl(extracted.colour)
|
|
199
|
+
|
|
196
200
|
hslColour.l = 1 - hslColour.l // culori uses 0-1 for lightness
|
|
197
201
|
const modifiedColour = formatHex(hslColour)
|
|
198
202
|
|
|
@@ -360,6 +364,7 @@ export default class Colour {
|
|
|
360
364
|
|
|
361
365
|
// memoize by raw inputs (strings) + normalized ratio
|
|
362
366
|
const key = mixKey(colourA, colourB, t)
|
|
367
|
+
|
|
363
368
|
if(_mixCache.has(key))
|
|
364
369
|
return _mixCache.get(key)
|
|
365
370
|
|
|
@@ -379,6 +384,7 @@ export default class Colour {
|
|
|
379
384
|
const out = (a < 1 ? formatHex8(withAlpha) : formatHex(mixed)).toLowerCase()
|
|
380
385
|
|
|
381
386
|
_mixCache.set(key, out)
|
|
387
|
+
|
|
382
388
|
return out
|
|
383
389
|
}
|
|
384
390
|
|
package/src/Command.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Sass from "./Sass.js"
|
|
2
2
|
import FileObject from "./FileObject.js"
|
|
3
|
+
import DirectoryObject from "./DirectoryObject.js"
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Base class for command-line interface commands.
|
|
@@ -19,7 +20,7 @@ export default class Command {
|
|
|
19
20
|
* Creates a new Command instance.
|
|
20
21
|
*
|
|
21
22
|
* @param {object} config - Configuration object
|
|
22
|
-
* @param {
|
|
23
|
+
* @param {DirectoryObject} config.cwd - Current working directory object
|
|
23
24
|
* @param {object} config.packageJson - Package.json data
|
|
24
25
|
*/
|
|
25
26
|
constructor({cwd,packageJson}) {
|
|
@@ -27,21 +28,12 @@ export default class Command {
|
|
|
27
28
|
this.#packageJson = packageJson
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
get cache() {
|
|
31
|
-
return this.#cache
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
set cache(cache) {
|
|
35
|
-
if(!this.#cache)
|
|
36
|
-
this.#cache = cache
|
|
37
|
-
}
|
|
38
|
-
|
|
39
31
|
/**
|
|
40
32
|
* Gets the current working directory object.
|
|
41
33
|
*
|
|
42
|
-
* @returns {
|
|
34
|
+
* @returns {DirectoryObject} The current working directory
|
|
43
35
|
*/
|
|
44
|
-
|
|
36
|
+
getCwd() {
|
|
45
37
|
return this.#cwd
|
|
46
38
|
}
|
|
47
39
|
|
|
@@ -50,44 +42,71 @@ export default class Command {
|
|
|
50
42
|
*
|
|
51
43
|
* @returns {object} The package.json object
|
|
52
44
|
*/
|
|
53
|
-
|
|
45
|
+
getPackageJson() {
|
|
54
46
|
return this.#packageJson
|
|
55
47
|
}
|
|
56
48
|
|
|
57
49
|
/**
|
|
58
|
-
*
|
|
50
|
+
* Sets the cache instance for the command.
|
|
59
51
|
*
|
|
60
|
-
* @
|
|
52
|
+
* @param {Cache} cache - The cache instance to set
|
|
53
|
+
* @returns {this} Returns this instance for method chaining
|
|
61
54
|
*/
|
|
62
|
-
|
|
63
|
-
|
|
55
|
+
setCache(cache) {
|
|
56
|
+
this.#cache = cache
|
|
57
|
+
|
|
58
|
+
return this
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Gets the cache instance.
|
|
63
|
+
*
|
|
64
|
+
* @returns {Cache|null} The cache instance or null if not set
|
|
65
|
+
*/
|
|
66
|
+
getCache() {
|
|
67
|
+
return this.#cache
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
/**
|
|
67
71
|
* Sets the CLI command string.
|
|
68
72
|
*
|
|
69
73
|
* @param {string} data - The CLI command string
|
|
74
|
+
* @returns {this} Returns this instance for method chaining
|
|
70
75
|
*/
|
|
71
|
-
|
|
76
|
+
setCliCommand(data) {
|
|
72
77
|
this.#cliCommand = data
|
|
78
|
+
|
|
79
|
+
return this
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
/**
|
|
76
|
-
* Gets the CLI
|
|
83
|
+
* Gets the CLI command string.
|
|
77
84
|
*
|
|
78
|
-
* @returns {
|
|
85
|
+
* @returns {string|null} The CLI command string
|
|
79
86
|
*/
|
|
80
|
-
|
|
81
|
-
return this.#
|
|
87
|
+
getCliCommand() {
|
|
88
|
+
return this.#cliCommand
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
/**
|
|
85
92
|
* Sets the CLI options object.
|
|
86
93
|
*
|
|
87
94
|
* @param {object} data - The CLI options configuration
|
|
95
|
+
* @returns {this} Returns this instance for method chaining
|
|
88
96
|
*/
|
|
89
|
-
|
|
97
|
+
setCliOptions(data) {
|
|
90
98
|
this.#cliOptions = data
|
|
99
|
+
|
|
100
|
+
return this
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Gets the CLI options object.
|
|
105
|
+
*
|
|
106
|
+
* @returns {object|null} The CLI options configuration
|
|
107
|
+
*/
|
|
108
|
+
getCliOptions() {
|
|
109
|
+
return this.#cliOptions
|
|
91
110
|
}
|
|
92
111
|
|
|
93
112
|
/**
|
|
@@ -95,10 +114,47 @@ export default class Command {
|
|
|
95
114
|
*
|
|
96
115
|
* @returns {string[]} Array of option names
|
|
97
116
|
*/
|
|
98
|
-
|
|
117
|
+
getCliOptionNames() {
|
|
99
118
|
return this.#optionNames
|
|
100
119
|
}
|
|
101
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Checks if the command has a cache instance.
|
|
123
|
+
*
|
|
124
|
+
* @returns {boolean} True if cache is available
|
|
125
|
+
*/
|
|
126
|
+
hasCache() {
|
|
127
|
+
return this.#cache !== null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Checks if the command has a CLI command string configured.
|
|
132
|
+
*
|
|
133
|
+
* @returns {boolean} True if CLI command is set
|
|
134
|
+
*/
|
|
135
|
+
hasCliCommand() {
|
|
136
|
+
return this.#cliCommand !== null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Checks if the command has CLI options configured.
|
|
141
|
+
*
|
|
142
|
+
* @returns {boolean} True if CLI options are set
|
|
143
|
+
*/
|
|
144
|
+
hasCliOptions() {
|
|
145
|
+
return this.#cliOptions !== null
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Checks if the command is ready to be built.
|
|
150
|
+
* Requires both CLI command and options to be set.
|
|
151
|
+
*
|
|
152
|
+
* @returns {boolean} True if command can be built
|
|
153
|
+
*/
|
|
154
|
+
canBuild() {
|
|
155
|
+
return this.hasCliCommand() && this.hasCliOptions()
|
|
156
|
+
}
|
|
157
|
+
|
|
102
158
|
/**
|
|
103
159
|
* Builds the CLI command interface using the commander.js program instance.
|
|
104
160
|
* Initializes the command with its options and action handler.
|
|
@@ -107,22 +163,24 @@ export default class Command {
|
|
|
107
163
|
* @returns {Promise<this>} Returns this instance for method chaining
|
|
108
164
|
*/
|
|
109
165
|
async buildCli(program) {
|
|
110
|
-
if(!this.
|
|
166
|
+
if(!this.hasCliCommand())
|
|
111
167
|
throw Sass.new("This command has no CLI command string.")
|
|
112
168
|
|
|
113
|
-
if(!this.
|
|
169
|
+
if(!this.hasCliOptions())
|
|
114
170
|
throw Sass.new("This command has no CLI options.")
|
|
115
171
|
|
|
116
|
-
this.#command = program.command(this.
|
|
172
|
+
this.#command = program.command(this.getCliCommand())
|
|
117
173
|
this.#command.action(async(...arg) => {
|
|
118
174
|
try {
|
|
119
175
|
await this.execute(...arg)
|
|
120
176
|
} catch(error) {
|
|
121
|
-
throw Sass.new(
|
|
177
|
+
throw Sass.new(
|
|
178
|
+
`Trying to execute ${this.constructor.name} with `+
|
|
179
|
+
`${JSON.stringify(...arg)}`, error)
|
|
122
180
|
}
|
|
123
181
|
})
|
|
124
182
|
|
|
125
|
-
this.addCliOptions(this.
|
|
183
|
+
this.addCliOptions(this.getCliOptions(), true)
|
|
126
184
|
|
|
127
185
|
return this
|
|
128
186
|
}
|
|
@@ -133,7 +191,7 @@ export default class Command {
|
|
|
133
191
|
* @param {string} name - The option name
|
|
134
192
|
* @param {string[]} options - Array containing option flag and description
|
|
135
193
|
* @param {boolean} preserve - Whether to preserve this option name in the list
|
|
136
|
-
* @returns {this} Returns this instance for method chaining
|
|
194
|
+
* @returns {Promise<this>} Returns this instance for method chaining
|
|
137
195
|
*/
|
|
138
196
|
addCliOption(name, options, preserve) {
|
|
139
197
|
if(!this.#command)
|
|
@@ -185,7 +243,7 @@ export default class Command {
|
|
|
185
243
|
* resolve or reject using Promise.allSettled().
|
|
186
244
|
*
|
|
187
245
|
* @param {string} event - The event name to emit
|
|
188
|
-
* @param {
|
|
246
|
+
* @param {...unknown} [arg] - Arguments to pass to event listeners
|
|
189
247
|
* @returns {Promise<void>} Resolves when all listeners have completed
|
|
190
248
|
*/
|
|
191
249
|
async asyncEmit(event, arg) {
|
|
@@ -193,7 +251,9 @@ export default class Command {
|
|
|
193
251
|
arg = arg || new Array()
|
|
194
252
|
const listeners = this.emitter.listeners(event)
|
|
195
253
|
|
|
196
|
-
const settled =
|
|
254
|
+
const settled =
|
|
255
|
+
await Promise.allSettled(listeners.map(listener => listener(arg)))
|
|
256
|
+
|
|
197
257
|
const rejected = settled.filter(reject => reject.status === "rejected")
|
|
198
258
|
|
|
199
259
|
if(rejected.length > 0) {
|
package/src/Compiler.js
CHANGED
|
@@ -29,12 +29,12 @@ export default class Compiler {
|
|
|
29
29
|
* Compiles a theme source file into a VS Code colour theme.
|
|
30
30
|
* Processes configuration, variables, imports, and theme definitions.
|
|
31
31
|
*
|
|
32
|
-
* @param {
|
|
32
|
+
* @param {Theme} theme - The file object containing source data and metadata
|
|
33
33
|
* @returns {Promise<void>} Resolves when compilation is complete
|
|
34
34
|
*/
|
|
35
35
|
async compile(theme) {
|
|
36
36
|
try {
|
|
37
|
-
const source = theme.
|
|
37
|
+
const source = theme.getSource()
|
|
38
38
|
const {config: sourceConfig} = source ?? {}
|
|
39
39
|
const {vars: sourceVars} = source
|
|
40
40
|
const {theme: sourceTheme} = source
|
|
@@ -43,6 +43,7 @@ export default class Compiler {
|
|
|
43
43
|
const evaluate = (...arg) => evaluator.evaluate(...arg)
|
|
44
44
|
|
|
45
45
|
const config = this.#decomposeObject(sourceConfig)
|
|
46
|
+
|
|
46
47
|
evaluate(config)
|
|
47
48
|
const recompConfig = this.#composeObject(config)
|
|
48
49
|
|
|
@@ -54,9 +55,12 @@ export default class Compiler {
|
|
|
54
55
|
|
|
55
56
|
// Let's get all of the imports!
|
|
56
57
|
const imports = recompConfig.import ?? []
|
|
57
|
-
const {imported,
|
|
58
|
+
const {imported,importByFile} =
|
|
59
|
+
await this.#import(imports, theme)
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
importByFile.forEach((themeData,file) => {
|
|
62
|
+
theme.addDependency(file,themeData)
|
|
63
|
+
})
|
|
60
64
|
|
|
61
65
|
// Handle tokenColors separately - imports first, then main source
|
|
62
66
|
// (append-only)
|
|
@@ -79,19 +83,28 @@ export default class Compiler {
|
|
|
79
83
|
|
|
80
84
|
// Shred them up! Kinda. And evaluate the variables in place
|
|
81
85
|
const vars = this.#decomposeObject(merged.vars)
|
|
86
|
+
|
|
82
87
|
evaluate(vars)
|
|
88
|
+
|
|
83
89
|
const workColors = this.#decomposeObject(merged.colors)
|
|
90
|
+
|
|
84
91
|
evaluate(workColors)
|
|
92
|
+
|
|
85
93
|
const workTokenColors = this.#decomposeObject(merged.tokenColors)
|
|
94
|
+
|
|
86
95
|
evaluate(workTokenColors)
|
|
87
|
-
|
|
96
|
+
|
|
97
|
+
const workSemanticTokenColors =
|
|
98
|
+
this.#decomposeObject(merged.semanticTokenColors)
|
|
99
|
+
|
|
88
100
|
evaluate(workSemanticTokenColors)
|
|
89
101
|
|
|
90
|
-
theme.
|
|
102
|
+
theme.setLookup(evaluator.lookup)
|
|
91
103
|
|
|
92
104
|
// Now let's do some reducing... into a form that works for VS Code
|
|
93
105
|
const reducer = (acc,curr) => {
|
|
94
106
|
acc[curr.flatPath] = curr.value
|
|
107
|
+
|
|
95
108
|
return acc
|
|
96
109
|
}
|
|
97
110
|
|
|
@@ -107,16 +120,16 @@ export default class Compiler {
|
|
|
107
120
|
sourceConfig.custom ?? {},
|
|
108
121
|
{
|
|
109
122
|
colors,
|
|
123
|
+
tokenColors,
|
|
110
124
|
semanticTokenColors,
|
|
111
|
-
tokenColors
|
|
112
125
|
}
|
|
113
126
|
)
|
|
114
127
|
|
|
115
128
|
// Voilà!
|
|
116
|
-
theme.output
|
|
117
|
-
theme.
|
|
129
|
+
theme.setOutput(output)
|
|
130
|
+
theme.setPool(evaluator.pool)
|
|
118
131
|
} catch(error) {
|
|
119
|
-
throw Sass.new(`Compiling ${theme.
|
|
132
|
+
throw Sass.new(`Compiling ${theme.getName()}`, error)
|
|
120
133
|
}
|
|
121
134
|
}
|
|
122
135
|
|
|
@@ -126,66 +139,78 @@ export default class Compiler {
|
|
|
126
139
|
*
|
|
127
140
|
* @param {Array<string>} imports - The import filenames.
|
|
128
141
|
* @param {Theme} theme - The theme object being compiled.
|
|
129
|
-
* @returns {Promise<object>} Object containing imported data and file references
|
|
142
|
+
* @returns {Promise<object,Map>} Object containing imported data and file references
|
|
130
143
|
*/
|
|
131
144
|
async #import(imports, theme) {
|
|
132
|
-
const importedFiles = []
|
|
133
145
|
const imported = {
|
|
134
146
|
vars: {},
|
|
135
147
|
colors: {},
|
|
136
|
-
tokenColors: []
|
|
148
|
+
tokenColors: [],
|
|
149
|
+
semanticTokenColors: {}
|
|
137
150
|
}
|
|
151
|
+
const importByFile = new Map()
|
|
138
152
|
|
|
139
153
|
imports = typeof imports === "string"
|
|
140
154
|
? [imports]
|
|
141
155
|
: imports
|
|
142
156
|
|
|
143
|
-
|
|
144
157
|
if(!Data.isArrayUniform(imports, "string"))
|
|
145
158
|
throw new Sass(
|
|
146
159
|
`All import entries must be strings. Got ${JSON.stringify(imports)}`
|
|
147
160
|
)
|
|
148
161
|
|
|
149
|
-
const loaded =
|
|
162
|
+
const loaded = new Map()
|
|
150
163
|
|
|
151
164
|
for(const importing of imports) {
|
|
152
165
|
try {
|
|
153
|
-
const file = new FileObject(importing, theme.
|
|
154
|
-
|
|
155
|
-
importedFiles.push(file)
|
|
166
|
+
const file = new FileObject(importing, theme.getSourceFile().directory)
|
|
156
167
|
|
|
157
168
|
// Get the cached version or a new version. Who knows? I don't know.
|
|
158
169
|
const {result, cost} = await Util.time(async() => {
|
|
159
|
-
return await theme.
|
|
170
|
+
return await theme.getCache().loadCachedData(file)
|
|
160
171
|
})
|
|
161
172
|
|
|
162
|
-
if(theme.
|
|
173
|
+
if(theme.getOptions().nerd) {
|
|
163
174
|
Term.status([
|
|
164
175
|
["muted", Util.rightAlignText(`${cost.toLocaleString()}ms`, 10), ["[","]"]],
|
|
165
176
|
"",
|
|
166
|
-
["muted", `${File.relativeOrAbsolutePath(theme.
|
|
167
|
-
["muted", `${theme.
|
|
168
|
-
], theme.
|
|
177
|
+
["muted", `${File.relativeOrAbsolutePath(theme.getCwd(),file)}`],
|
|
178
|
+
["muted", `${theme.getName()}`,["(",")"]],
|
|
179
|
+
], theme.getOptions())
|
|
169
180
|
}
|
|
170
181
|
|
|
171
|
-
if(result)
|
|
172
|
-
loaded.
|
|
173
|
-
|
|
182
|
+
if(result)
|
|
183
|
+
loaded.set(file, result)
|
|
184
|
+
|
|
174
185
|
} catch(error) {
|
|
175
186
|
throw Sass.new(`Attempting to import ${importing}`, error)
|
|
176
187
|
}
|
|
177
188
|
}
|
|
178
189
|
|
|
179
|
-
loaded.forEach(
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
190
|
+
loaded.forEach((load, file) => {
|
|
191
|
+
const vars = load?.vars ?? {}
|
|
192
|
+
const colors = load?.theme?.colors ?? {}
|
|
193
|
+
const tokenColors = load?.theme?.tokenColors ?? []
|
|
194
|
+
const semanticTokenColors = load?.theme?.semanticTokenColors ?? {}
|
|
195
|
+
|
|
196
|
+
importByFile.set(file, new Map([
|
|
197
|
+
["vars", vars],
|
|
198
|
+
["colors", colors],
|
|
199
|
+
["tokenColors", tokenColors],
|
|
200
|
+
["semanticTokenColors", semanticTokenColors]
|
|
201
|
+
]))
|
|
202
|
+
|
|
203
|
+
imported.vars =
|
|
204
|
+
Data.mergeObject(imported.vars, vars)
|
|
205
|
+
imported.colors =
|
|
206
|
+
Data.mergeObject(imported.colors, colors)
|
|
207
|
+
imported.tokenColors =
|
|
208
|
+
[...imported.tokenColors, ...tokenColors]
|
|
209
|
+
imported.semanticTokenColors =
|
|
210
|
+
Data.mergeObject(imported.semanticTokenColors, semanticTokenColors)
|
|
186
211
|
})
|
|
187
212
|
|
|
188
|
-
return {imported,
|
|
213
|
+
return {imported,importByFile}
|
|
189
214
|
}
|
|
190
215
|
|
|
191
216
|
/**
|
|
@@ -209,8 +234,9 @@ export default class Compiler {
|
|
|
209
234
|
if(isObject(item)) {
|
|
210
235
|
result.push(...this.#decomposeObject(work[key], currPath))
|
|
211
236
|
} else if(Array.isArray(work[key])) {
|
|
212
|
-
|
|
237
|
+
work[key].forEach((item, index) => {
|
|
213
238
|
const path = [...currPath, String(index+1)]
|
|
239
|
+
|
|
214
240
|
result.push({
|
|
215
241
|
key,
|
|
216
242
|
value: String(item),
|
|
@@ -308,7 +334,7 @@ export default class Compiler {
|
|
|
308
334
|
* Checks if a value is a plain object (not null or array).
|
|
309
335
|
* Utility method for type checking during compilation.
|
|
310
336
|
*
|
|
311
|
-
* @param {
|
|
337
|
+
* @param {unknown} value - The value to check
|
|
312
338
|
* @returns {boolean} True if the value is a plain object
|
|
313
339
|
*/
|
|
314
340
|
#isObject(value) {
|