@gesslar/sassy 0.21.0 → 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/package.json +1 -1
- package/src/Command.js +1 -0
- package/src/Data.js +2 -2
- package/src/Evaluator.js +52 -3
- package/src/LintCommand.js +215 -257
- package/src/ResolveCommand.js +16 -2
- package/src/Session.js +3 -1
- package/src/Theme.js +19 -4
- package/src/cli.js +2 -1
package/package.json
CHANGED
package/src/Command.js
CHANGED
package/src/Data.js
CHANGED
|
@@ -429,7 +429,7 @@ export default class Data {
|
|
|
429
429
|
const value = obj[name]
|
|
430
430
|
|
|
431
431
|
// Recursively freeze nested objects
|
|
432
|
-
if(
|
|
432
|
+
if(typeof value === "object" && value !== null)
|
|
433
433
|
Data.deepFreezeObject(value)
|
|
434
434
|
}
|
|
435
435
|
|
|
@@ -483,7 +483,7 @@ export default class Data {
|
|
|
483
483
|
* @returns {object} The merged object
|
|
484
484
|
*/
|
|
485
485
|
static mergeObject(...sources) {
|
|
486
|
-
const isObject = obj =>
|
|
486
|
+
const isObject = obj => typeof obj === "object" && obj !== null && !Array.isArray(obj)
|
|
487
487
|
|
|
488
488
|
return sources.reduce((acc, obj) => {
|
|
489
489
|
if(!isObject(obj))
|
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
|
|
@@ -81,6 +112,18 @@ export default class Evaluator {
|
|
|
81
112
|
* - No return value. Evident by the absence of a return statement.
|
|
82
113
|
*
|
|
83
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
|
|
@@ -229,8 +272,8 @@ export default class Evaluator {
|
|
|
229
272
|
* @returns {ThemeToken|null} The resolved token or null.
|
|
230
273
|
*/
|
|
231
274
|
#resolveVariable(value) {
|
|
232
|
-
const {captured
|
|
233
|
-
const work =
|
|
275
|
+
const {captured} = Evaluator.sub.exec(value).groups
|
|
276
|
+
const work = Evaluator.extractVariableName(value)
|
|
234
277
|
const existing = this.#pool.findToken(work)
|
|
235
278
|
|
|
236
279
|
if(!existing)
|
|
@@ -253,7 +296,13 @@ export default class Evaluator {
|
|
|
253
296
|
* @returns {ThemeToken|null} The resolved token or null.
|
|
254
297
|
*/
|
|
255
298
|
#resolveFunction(value) {
|
|
256
|
-
const {captured
|
|
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
|
|
257
306
|
const split = args?.split(",").map(a => a.trim()) ?? []
|
|
258
307
|
|
|
259
308
|
// Look up source tokens for arguments to preserve color space
|
package/src/LintCommand.js
CHANGED
|
@@ -21,7 +21,6 @@ import c from "@gesslar/colours"
|
|
|
21
21
|
import Command from "./Command.js"
|
|
22
22
|
import Evaluator from "./Evaluator.js"
|
|
23
23
|
import File from "./File.js"
|
|
24
|
-
import FileObject from "./FileObject.js"
|
|
25
24
|
import Term from "./Term.js"
|
|
26
25
|
import Theme from "./Theme.js"
|
|
27
26
|
import ThemePool from "./ThemePool.js"
|
|
@@ -36,6 +35,36 @@ import ThemePool from "./ThemePool.js"
|
|
|
36
35
|
* behaviour.
|
|
37
36
|
*/
|
|
38
37
|
export default class LintCommand extends Command {
|
|
38
|
+
|
|
39
|
+
// Theme section constants
|
|
40
|
+
static SECTIONS = {
|
|
41
|
+
VARS: "vars",
|
|
42
|
+
COLORS: "colors",
|
|
43
|
+
TOKEN_COLORS: "tokenColors",
|
|
44
|
+
SEMANTIC_TOKEN_COLORS: "semanticTokenColors"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Issue severity levels
|
|
48
|
+
static SEVERITY = {
|
|
49
|
+
HIGH: "high",
|
|
50
|
+
MEDIUM: "medium",
|
|
51
|
+
LOW: "low"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Issue type constants
|
|
55
|
+
static ISSUE_TYPES = {
|
|
56
|
+
DUPLICATE_SCOPE: "duplicate-scope",
|
|
57
|
+
UNDEFINED_VARIABLE: "undefined-variable",
|
|
58
|
+
UNUSED_VARIABLE: "unused-variable",
|
|
59
|
+
PRECEDENCE_ISSUE: "precedence-issue"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Template strings for dynamic rule names
|
|
63
|
+
static TEMPLATES = {
|
|
64
|
+
ENTRY_NAME: index => `Entry ${index + 1}`,
|
|
65
|
+
OBJECT_NAME: index => `Object ${index + 1}`,
|
|
66
|
+
VARIABLE_PREFIX: "$"
|
|
67
|
+
}
|
|
39
68
|
/**
|
|
40
69
|
* Creates a new LintCommand instance.
|
|
41
70
|
*
|
|
@@ -86,34 +115,40 @@ export default class LintCommand extends Command {
|
|
|
86
115
|
*/
|
|
87
116
|
async lint(theme) {
|
|
88
117
|
const results = {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
118
|
+
[LC.SECTIONS.TOKEN_COLORS]: [],
|
|
119
|
+
[LC.SECTIONS.SEMANTIC_TOKEN_COLORS]: [],
|
|
120
|
+
[LC.SECTIONS.COLORS]: [],
|
|
92
121
|
variables: []
|
|
93
122
|
}
|
|
94
123
|
|
|
95
|
-
const
|
|
124
|
+
const output = theme.getOutput()
|
|
96
125
|
|
|
97
126
|
// Always perform structural linting (works with or without pool)
|
|
98
|
-
if(
|
|
99
|
-
results.
|
|
100
|
-
...this.#lintTokenColorsStructure(
|
|
101
|
-
|
|
127
|
+
if(output?.tokenColors)
|
|
128
|
+
results[LC.SECTIONS.TOKEN_COLORS]
|
|
129
|
+
.push(...this.#lintTokenColorsStructure(output.tokenColors))
|
|
130
|
+
|
|
131
|
+
const pool = theme.getPool()
|
|
102
132
|
|
|
103
133
|
// Only perform variable-dependent linting if pool exists
|
|
104
134
|
if(pool) {
|
|
105
135
|
// Get source data for variable analysis
|
|
106
|
-
const colors =
|
|
107
|
-
|
|
108
|
-
const
|
|
136
|
+
const colors = this.#getSection(
|
|
137
|
+
theme, LC.SECTIONS.COLORS)
|
|
138
|
+
const tokenColorTuples = this.#getSection(
|
|
139
|
+
theme, LC.SECTIONS.TOKEN_COLORS)
|
|
140
|
+
const semanticTokenColors = this.#getSection(
|
|
141
|
+
theme, LC.SECTIONS.SEMANTIC_TOKEN_COLORS)
|
|
109
142
|
|
|
110
143
|
// Variable-dependent linting
|
|
111
|
-
results.
|
|
112
|
-
|
|
113
|
-
results.
|
|
114
|
-
...this.#
|
|
115
|
-
|
|
116
|
-
|
|
144
|
+
results[LC.SECTIONS.COLORS]
|
|
145
|
+
.push(...this.#lintColors(colors, pool))
|
|
146
|
+
results[LC.SECTIONS.TOKEN_COLORS]
|
|
147
|
+
.push(...this.#lintTokenColors(tokenColorTuples, pool))
|
|
148
|
+
results[LC.SECTIONS.SEMANTIC_TOKEN_COLORS]
|
|
149
|
+
.push(...this.#lintSemanticTokenColors(semanticTokenColors, pool))
|
|
150
|
+
results.variables
|
|
151
|
+
.push(...await this.#lintVariables(theme, pool))
|
|
117
152
|
}
|
|
118
153
|
|
|
119
154
|
return results
|
|
@@ -140,44 +175,71 @@ export default class LintCommand extends Command {
|
|
|
140
175
|
* Performs variable-dependent linting of tokenColors data.
|
|
141
176
|
* Checks for undefined variable references.
|
|
142
177
|
*
|
|
143
|
-
* @param {Array}
|
|
178
|
+
* @param {Array<[object, Array]>} tokenColorTuples - Array of [file, tokenColors] tuples
|
|
144
179
|
* @param {ThemePool} pool - The theme's variable pool
|
|
145
180
|
* @returns {Array} Array of variable-related issues
|
|
181
|
+
* @private
|
|
146
182
|
*/
|
|
147
|
-
lintTokenColors(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
183
|
+
#lintTokenColors(tokenColorTuples, pool) {
|
|
184
|
+
if(tokenColorTuples.length === 0)
|
|
185
|
+
return []
|
|
186
|
+
|
|
187
|
+
const issues = []
|
|
188
|
+
|
|
189
|
+
for(const [_, tokenColors] of tokenColorTuples) {
|
|
190
|
+
if(Array.isArray(tokenColors))
|
|
191
|
+
issues.push(
|
|
192
|
+
...this.#checkUndefinedVariables(
|
|
193
|
+
tokenColors, pool, LC.SECTIONS.TOKEN_COLORS))
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return issues
|
|
151
197
|
}
|
|
152
198
|
|
|
153
199
|
/**
|
|
154
200
|
* Performs variable-dependent linting of semanticTokenColors data.
|
|
155
201
|
* Checks for undefined variable references.
|
|
156
202
|
*
|
|
157
|
-
* @param {Array}
|
|
203
|
+
* @param {Array<[object, object]>} semanticTokenColorTuples - Array of [file, semanticTokenColors] tuples
|
|
158
204
|
* @param {ThemePool} pool - The theme's variable pool
|
|
159
205
|
* @returns {Array} Array of variable-related issues
|
|
160
206
|
* @private
|
|
161
207
|
*/
|
|
162
|
-
#lintSemanticTokenColors(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
208
|
+
#lintSemanticTokenColors(semanticTokenColorTuples, pool) {
|
|
209
|
+
if(semanticTokenColorTuples.length === 0)
|
|
210
|
+
return []
|
|
211
|
+
|
|
212
|
+
const issues = []
|
|
213
|
+
|
|
214
|
+
for(const [_, semanticTokenColors] of semanticTokenColorTuples)
|
|
215
|
+
issues.push(...this.#checkUndefinedVariables(
|
|
216
|
+
[semanticTokenColors], pool, LC.SECTIONS.SEMANTIC_TOKEN_COLORS)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
return issues
|
|
166
220
|
}
|
|
167
221
|
|
|
168
222
|
/**
|
|
169
223
|
* Performs variable-dependent linting of colors data.
|
|
170
224
|
* Checks for undefined variable references.
|
|
171
225
|
*
|
|
172
|
-
* @param {Array}
|
|
226
|
+
* @param {Array<[object, object]>} colorTuples - Array of [file, colors] tuples
|
|
173
227
|
* @param {ThemePool} pool - The theme's variable pool
|
|
174
228
|
* @returns {Array} Array of variable-related issues
|
|
175
229
|
* @private
|
|
176
230
|
*/
|
|
177
|
-
#lintColors(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
231
|
+
#lintColors(colorTuples, pool) {
|
|
232
|
+
if(colorTuples.length === 0)
|
|
233
|
+
return []
|
|
234
|
+
|
|
235
|
+
const issues = []
|
|
236
|
+
|
|
237
|
+
for(const [_, colors] of colorTuples)
|
|
238
|
+
issues.push(...this.#checkUndefinedVariables(
|
|
239
|
+
[colors], pool, LC.SECTIONS.COLORS)
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
return issues
|
|
181
243
|
}
|
|
182
244
|
|
|
183
245
|
/**
|
|
@@ -186,12 +248,12 @@ export default class LintCommand extends Command {
|
|
|
186
248
|
*
|
|
187
249
|
* @param {Theme} theme - The theme object
|
|
188
250
|
* @param {ThemePool} pool - The theme's variable pool
|
|
189
|
-
* @returns {Array} Array of unused variable issues
|
|
251
|
+
* @returns {Promise<Array>} Array of unused variable issues
|
|
190
252
|
* @private
|
|
191
253
|
*/
|
|
192
|
-
#lintVariables(theme, pool) {
|
|
254
|
+
async #lintVariables(theme, pool) {
|
|
193
255
|
return pool
|
|
194
|
-
? this.#checkUnusedVariables(theme, pool)
|
|
256
|
+
? await this.#checkUnusedVariables(theme, pool)
|
|
195
257
|
: []
|
|
196
258
|
}
|
|
197
259
|
|
|
@@ -208,123 +270,33 @@ export default class LintCommand extends Command {
|
|
|
208
270
|
|
|
209
271
|
// Flatten all results into a single array for backward compatibility
|
|
210
272
|
return [
|
|
211
|
-
...results.
|
|
212
|
-
...results.
|
|
213
|
-
...results.
|
|
273
|
+
...results[LC.SECTIONS.TOKEN_COLORS],
|
|
274
|
+
...results[LC.SECTIONS.SEMANTIC_TOKEN_COLORS],
|
|
275
|
+
...results[LC.SECTIONS.COLORS],
|
|
214
276
|
...results.variables
|
|
215
277
|
]
|
|
216
278
|
}
|
|
217
279
|
|
|
218
280
|
/**
|
|
219
|
-
* Extracts
|
|
220
|
-
* dependencies.
|
|
281
|
+
* Extracts a specific section from all theme dependencies (including main theme).
|
|
221
282
|
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
283
|
+
* Returns an array of [FileObject, sectionData] tuples for linting methods that need
|
|
284
|
+
* to track which file each piece of data originated from for proper error reporting.
|
|
224
285
|
*
|
|
225
|
-
* @param {Theme} theme - The
|
|
226
|
-
* @
|
|
286
|
+
* @param {Theme} theme - The theme object with dependencies
|
|
287
|
+
* @param {string} section - The section name to extract (vars, colors, tokenColors, semanticTokenColors)
|
|
288
|
+
* @returns {Array<[object, object|Array]>} Array of [file, sectionData] tuples
|
|
227
289
|
* @private
|
|
228
290
|
*/
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
// Get tokenColors from main theme source
|
|
233
|
-
if(theme.getSource()?.theme?.tokenColors)
|
|
234
|
-
sourceTokenColors.push(...theme.getSource().theme.tokenColors)
|
|
235
|
-
|
|
236
|
-
// Get tokenColors from imported files
|
|
237
|
-
if(theme.hasDependencies()) {
|
|
238
|
-
for(const dependency of theme.getDependencies()) {
|
|
239
|
-
// Skip main file, already processed
|
|
240
|
-
if(dependency.getSourceFile().path !== theme.getSourceFile().path) {
|
|
241
|
-
try {
|
|
242
|
-
const depData = await theme.getCache().loadCachedData(dependency.getSourceFile())
|
|
243
|
-
|
|
244
|
-
if(depData?.theme?.tokenColors)
|
|
245
|
-
sourceTokenColors.push(...depData.theme.tokenColors)
|
|
246
|
-
} catch {
|
|
247
|
-
// nothing to see here.
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
291
|
+
#getSection(theme, section) {
|
|
292
|
+
return Array.from(theme.getDependencies()).map(dep => {
|
|
293
|
+
const source = dep.getSource()
|
|
252
294
|
|
|
253
|
-
|
|
254
|
-
|
|
295
|
+
if(source?.has(section))
|
|
296
|
+
return [dep.getSourceFile(),source.get(section)]
|
|
255
297
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
* and dependencies.
|
|
259
|
-
*
|
|
260
|
-
* Used for variable analysis since we need the uncompiled data with variable
|
|
261
|
-
* references.
|
|
262
|
-
*
|
|
263
|
-
* @param {Theme} theme - The compiled theme object (contains both output and source)
|
|
264
|
-
* @returns {Promise<Array>} Array of source semanticTokenColors entries with variables intact
|
|
265
|
-
*/
|
|
266
|
-
async getSemanticTokenColors(theme) {
|
|
267
|
-
const sourceSemanticTokenColors = []
|
|
268
|
-
|
|
269
|
-
// Get semanticTokenColors from main theme source
|
|
270
|
-
if(theme.sourceHasSemanticTokenColors())
|
|
271
|
-
sourceSemanticTokenColors.push(theme.getSourceSemanticTokenColors())
|
|
272
|
-
|
|
273
|
-
// Get semanticTokenColors from imported files
|
|
274
|
-
if(theme.hasDependencies()) {
|
|
275
|
-
for(const dependency of theme.getDependencies()) {
|
|
276
|
-
// Skip main file, already processed
|
|
277
|
-
if(dependency.getSourceFile().path !== theme.getSourceFile().path) {
|
|
278
|
-
try {
|
|
279
|
-
const depData = await theme.getCache().loadCachedData(dependency.getSourceFile())
|
|
280
|
-
|
|
281
|
-
if(depData?.theme?.semanticTokenColors)
|
|
282
|
-
sourceSemanticTokenColors.push(depData.theme.semanticTokenColors)
|
|
283
|
-
} catch {
|
|
284
|
-
// nothing to see here.
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return sourceSemanticTokenColors
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Extracts the original source colors data from theme.source and
|
|
295
|
-
* dependencies.
|
|
296
|
-
*
|
|
297
|
-
* Used for variable analysis since we need the uncompiled data with variable
|
|
298
|
-
* references.
|
|
299
|
-
*
|
|
300
|
-
* @param {Theme} theme - The compiled theme object (contains both output and source)
|
|
301
|
-
* @returns {Promise<Array>} Array of source colors entries with variables intact
|
|
302
|
-
*/
|
|
303
|
-
async getColors(theme) {
|
|
304
|
-
const sourceColors = []
|
|
305
|
-
|
|
306
|
-
// Get colors from main theme source
|
|
307
|
-
if(theme.getSource()?.theme?.colors)
|
|
308
|
-
sourceColors.push(theme.getSource().theme.colors)
|
|
309
|
-
|
|
310
|
-
// Get colors from imported files
|
|
311
|
-
if(theme.hasDependencies()) {
|
|
312
|
-
for(const dependency of theme.getDependencies()) {
|
|
313
|
-
// Skip main file, already processed
|
|
314
|
-
if(dependency.getSourceFile().path !== theme.getSourceFile().path) {
|
|
315
|
-
try {
|
|
316
|
-
const depData = await theme.getCache().loadCachedData(dependency.getSourceFile())
|
|
317
|
-
|
|
318
|
-
if(depData?.theme?.colors)
|
|
319
|
-
sourceColors.push(depData.theme.colors)
|
|
320
|
-
} catch {
|
|
321
|
-
// nothing to see here.
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return sourceColors
|
|
298
|
+
return false
|
|
299
|
+
}).filter(Boolean)
|
|
328
300
|
}
|
|
329
301
|
|
|
330
302
|
/**
|
|
@@ -340,10 +312,9 @@ export default class LintCommand extends Command {
|
|
|
340
312
|
return
|
|
341
313
|
}
|
|
342
314
|
|
|
343
|
-
const errors = issues.filter(i => i.severity ===
|
|
344
|
-
const warnings = issues.filter(i => i.severity ===
|
|
345
|
-
const infos = issues.filter(i => i.severity ===
|
|
346
|
-
|
|
315
|
+
const errors = issues.filter(i => i.severity === LC.SEVERITY.HIGH)
|
|
316
|
+
const warnings = issues.filter(i => i.severity === LC.SEVERITY.MEDIUM)
|
|
317
|
+
const infos = issues.filter(i => i.severity === LC.SEVERITY.LOW)
|
|
347
318
|
const allIssues = errors.concat(warnings, infos)
|
|
348
319
|
|
|
349
320
|
allIssues.forEach(issue => this.#reportSingleIssue(issue))
|
|
@@ -372,9 +343,9 @@ export default class LintCommand extends Command {
|
|
|
372
343
|
*/
|
|
373
344
|
#getIndicator(severity) {
|
|
374
345
|
switch(severity) {
|
|
375
|
-
case
|
|
376
|
-
case
|
|
377
|
-
case
|
|
346
|
+
case LC.SEVERITY.HIGH: return c`{error}●{/}`
|
|
347
|
+
case LC.SEVERITY.MEDIUM: return c`{warn}●{/}`
|
|
348
|
+
case LC.SEVERITY.LOW:
|
|
378
349
|
default: return c`{info}●{/}`
|
|
379
350
|
}
|
|
380
351
|
}
|
|
@@ -389,26 +360,26 @@ export default class LintCommand extends Command {
|
|
|
389
360
|
const indicator = this.#getIndicator(issue.severity)
|
|
390
361
|
|
|
391
362
|
switch(issue.type) {
|
|
392
|
-
case
|
|
363
|
+
case LC.ISSUE_TYPES.DUPLICATE_SCOPE: {
|
|
393
364
|
const rules = issue.occurrences.map(occ => `{loc}'${occ.name}{/}'`).join(", ")
|
|
394
365
|
|
|
395
366
|
Term.info(c`${indicator} Scope '{context}${issue.scope}{/}' is duplicated in ${rules}`)
|
|
396
367
|
break
|
|
397
368
|
}
|
|
398
369
|
|
|
399
|
-
case
|
|
400
|
-
const sectionInfo = issue.section && issue.section !==
|
|
370
|
+
case LC.ISSUE_TYPES.UNDEFINED_VARIABLE: {
|
|
371
|
+
const sectionInfo = issue.section && issue.section !== LC.SECTIONS.TOKEN_COLORS ? ` in ${issue.section}` : ""
|
|
401
372
|
|
|
402
373
|
Term.info(c`${indicator} Variable '{context}${issue.variable}{/}' is used but not defined in '${issue.rule}' (${issue.property} property)${sectionInfo}`)
|
|
403
374
|
break
|
|
404
375
|
}
|
|
405
376
|
|
|
406
|
-
case
|
|
407
|
-
Term.info(c`${indicator} Variable '{context}${issue.variable}{/}' is defined in '{loc}${issue.
|
|
377
|
+
case LC.ISSUE_TYPES.UNUSED_VARIABLE: {
|
|
378
|
+
Term.info(c`${indicator} Variable '{context}${issue.variable}{/}' is defined in '{loc}${issue.occurrence}{/}', but is never used`)
|
|
408
379
|
break
|
|
409
380
|
}
|
|
410
381
|
|
|
411
|
-
case
|
|
382
|
+
case LC.ISSUE_TYPES.PRECEDENCE_ISSUE: {
|
|
412
383
|
if(issue.broadIndex === issue.specificIndex) {
|
|
413
384
|
Term.info(c`${indicator} Scope '{context}${issue.broadScope}{/}' makes more specific '{context}${issue.specificScope}{/}' redundant in '{loc}${issue.broadRule}{/}'`)
|
|
414
385
|
} else {
|
|
@@ -439,13 +410,12 @@ export default class LintCommand extends Command {
|
|
|
439
410
|
const scopes = entry.scope.split(",").map(s => s.trim())
|
|
440
411
|
|
|
441
412
|
scopes.forEach(scope => {
|
|
442
|
-
if(!scopeOccurrences.has(scope))
|
|
413
|
+
if(!scopeOccurrences.has(scope))
|
|
443
414
|
scopeOccurrences.set(scope, [])
|
|
444
|
-
}
|
|
445
415
|
|
|
446
416
|
scopeOccurrences.get(scope).push({
|
|
447
417
|
index: index + 1,
|
|
448
|
-
name: entry.name ||
|
|
418
|
+
name: entry.name || LC.TEMPLATES.ENTRY_NAME(index),
|
|
449
419
|
entry
|
|
450
420
|
})
|
|
451
421
|
})
|
|
@@ -455,8 +425,8 @@ export default class LintCommand extends Command {
|
|
|
455
425
|
for(const [scope, occurrences] of scopeOccurrences) {
|
|
456
426
|
if(occurrences.length > 1) {
|
|
457
427
|
issues.push({
|
|
458
|
-
type:
|
|
459
|
-
severity:
|
|
428
|
+
type: LC.ISSUE_TYPES.DUPLICATE_SCOPE,
|
|
429
|
+
severity: LC.SEVERITY.MEDIUM,
|
|
460
430
|
scope,
|
|
461
431
|
occurrences
|
|
462
432
|
})
|
|
@@ -476,28 +446,27 @@ export default class LintCommand extends Command {
|
|
|
476
446
|
* @returns {Array} Array of undefined variable issues
|
|
477
447
|
* @private
|
|
478
448
|
*/
|
|
479
|
-
#checkUndefinedVariables(themeData, pool, section
|
|
449
|
+
#checkUndefinedVariables(themeData, pool, section=LC.SECTIONS.TOKEN_COLORS) {
|
|
480
450
|
const issues = []
|
|
481
451
|
const definedVars = pool ? new Set(pool.getTokens().keys()) : new Set()
|
|
482
452
|
|
|
483
|
-
if(section ===
|
|
453
|
+
if(section === LC.SECTIONS.TOKEN_COLORS && Array.isArray(themeData)) {
|
|
484
454
|
themeData.forEach((entry, index) => {
|
|
485
455
|
const settings = entry.settings || {}
|
|
486
456
|
|
|
487
457
|
for(const [key, value] of Object.entries(settings)) {
|
|
488
458
|
if(typeof value === "string") {
|
|
489
|
-
const
|
|
490
|
-
const varName = none || parens || braces
|
|
459
|
+
const varName = Evaluator.extractVariableName(value)
|
|
491
460
|
|
|
492
461
|
if(!varName)
|
|
493
462
|
return
|
|
494
463
|
|
|
495
464
|
if(!definedVars.has(varName)) {
|
|
496
465
|
issues.push({
|
|
497
|
-
type:
|
|
498
|
-
severity:
|
|
466
|
+
type: LC.ISSUE_TYPES.UNDEFINED_VARIABLE,
|
|
467
|
+
severity: LC.SEVERITY.HIGH,
|
|
499
468
|
variable: value,
|
|
500
|
-
rule: entry.name ||
|
|
469
|
+
rule: entry.name || LC.TEMPLATES.ENTRY_NAME(index),
|
|
501
470
|
property: key,
|
|
502
471
|
section
|
|
503
472
|
})
|
|
@@ -505,12 +474,18 @@ export default class LintCommand extends Command {
|
|
|
505
474
|
}
|
|
506
475
|
}
|
|
507
476
|
})
|
|
508
|
-
} else if((section ===
|
|
477
|
+
} else if((section === LC.SECTIONS.SEMANTIC_TOKEN_COLORS ||
|
|
478
|
+
section === LC.SECTIONS.COLORS)
|
|
479
|
+
&& Array.isArray(themeData)) {
|
|
509
480
|
// Handle semanticTokenColors and colors as objects
|
|
510
481
|
themeData.forEach((dataObject, objIndex) => {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
482
|
+
this.#checkObjectForUndefinedVariables(
|
|
483
|
+
dataObject,
|
|
484
|
+
definedVars,
|
|
485
|
+
issues,
|
|
486
|
+
section,
|
|
487
|
+
LC.TEMPLATES.OBJECT_NAME(objIndex)
|
|
488
|
+
)
|
|
514
489
|
})
|
|
515
490
|
}
|
|
516
491
|
|
|
@@ -529,24 +504,23 @@ export default class LintCommand extends Command {
|
|
|
529
504
|
* @private
|
|
530
505
|
*/
|
|
531
506
|
#checkObjectForUndefinedVariables(obj, definedVars, issues, section, ruleName, path = "") {
|
|
532
|
-
for(const [key, value] of Object.entries(obj)) {
|
|
507
|
+
for(const [key, value] of Object.entries(obj ?? {})) {
|
|
533
508
|
const currentPath = path ? `${path}.${key}` : key
|
|
534
509
|
|
|
535
510
|
if(typeof value === "string") {
|
|
536
|
-
const
|
|
537
|
-
const varName = none || parens || braces
|
|
511
|
+
const varName = Evaluator.extractVariableName(value)
|
|
538
512
|
|
|
539
513
|
if(varName && !definedVars.has(varName)) {
|
|
540
514
|
issues.push({
|
|
541
|
-
type:
|
|
542
|
-
severity:
|
|
515
|
+
type: LC.ISSUE_TYPES.UNDEFINED_VARIABLE,
|
|
516
|
+
severity: LC.SEVERITY.HIGH,
|
|
543
517
|
variable: value,
|
|
544
518
|
rule: ruleName,
|
|
545
519
|
property: currentPath,
|
|
546
520
|
section
|
|
547
521
|
})
|
|
548
522
|
}
|
|
549
|
-
} else if(
|
|
523
|
+
} else if(typeof value === "object" && !Array.isArray(value)) {
|
|
550
524
|
this.#checkObjectForUndefinedVariables(
|
|
551
525
|
value, definedVars, issues, section, ruleName, currentPath
|
|
552
526
|
)
|
|
@@ -562,10 +536,10 @@ export default class LintCommand extends Command {
|
|
|
562
536
|
*
|
|
563
537
|
* @param {Theme} theme - The compiled theme object
|
|
564
538
|
* @param {ThemePool} pool - The theme's variable pool
|
|
565
|
-
* @returns {Array} Array of unused variable issues
|
|
539
|
+
* @returns {Promise<Array>} Array of unused variable issues
|
|
566
540
|
* @private
|
|
567
541
|
*/
|
|
568
|
-
#checkUnusedVariables(theme, pool) {
|
|
542
|
+
async #checkUnusedVariables(theme, pool) {
|
|
569
543
|
const issues = []
|
|
570
544
|
|
|
571
545
|
if(!pool || !theme.getSource())
|
|
@@ -574,67 +548,36 @@ export default class LintCommand extends Command {
|
|
|
574
548
|
// Get variables defined in the vars section only
|
|
575
549
|
const definedVars = new Map()
|
|
576
550
|
const cwd = this.getCwd()
|
|
577
|
-
const mainFile = new FileObject(theme.getSourceFile().path)
|
|
578
|
-
const relativeMainPath = File.relativeOrAbsolutePath(cwd, mainFile)
|
|
579
|
-
|
|
580
|
-
this.#collectVarsDefinitions(theme.getSource().vars, definedVars, "", relativeMainPath)
|
|
581
|
-
|
|
582
|
-
// Also check dependencies for vars definitions
|
|
583
|
-
if(theme.hasDependencies()) {
|
|
584
|
-
for(const dependency of theme.getDependencies()) {
|
|
585
|
-
try {
|
|
586
|
-
const depData = theme.getCache()?.loadCachedDataSync?.(dependency.getSourceFile())
|
|
587
|
-
|
|
588
|
-
if(depData?.vars) {
|
|
589
|
-
const depFile = new FileObject(dependency.getSourceFile().path)
|
|
590
|
-
const relativeDependencyPath = File.relativeOrAbsolutePath(
|
|
591
|
-
cwd, depFile
|
|
592
|
-
)
|
|
593
|
-
|
|
594
|
-
this.#collectVarsDefinitions(depData.vars, definedVars, "", relativeDependencyPath)
|
|
595
|
-
}
|
|
596
|
-
} catch {
|
|
597
|
-
// Ignore cache errors
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
551
|
|
|
602
552
|
const usedVars = new Set()
|
|
603
553
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if(theme.hasDependencies()) {
|
|
621
|
-
for(const dependency of theme.getDependencies()) {
|
|
622
|
-
try {
|
|
623
|
-
const depData = theme.getCache()?.loadCachedDataSync?.(dependency.getSourceFile())
|
|
624
|
-
|
|
625
|
-
if(depData) {
|
|
626
|
-
if(depData.colors)
|
|
627
|
-
this.#findVariableUsage(depData.colors, usedVars)
|
|
628
|
-
|
|
629
|
-
if(depData.tokenColors)
|
|
630
|
-
this.#findVariableUsage(depData.tokenColors, usedVars)
|
|
631
|
-
|
|
632
|
-
if(depData.semanticColors)
|
|
633
|
-
this.#findVariableUsage(depData.semanticColors, usedVars)
|
|
634
|
-
}
|
|
635
|
-
} catch {
|
|
636
|
-
// Ignore cache errors
|
|
554
|
+
for(const dependency of theme.getDependencies()) {
|
|
555
|
+
try {
|
|
556
|
+
const depData = dependency.getSource()
|
|
557
|
+
const depFile = dependency.getSourceFile()
|
|
558
|
+
|
|
559
|
+
// Collect vars definitions
|
|
560
|
+
if(depData?.vars) {
|
|
561
|
+
const relativeDependencyPath =
|
|
562
|
+
File.relativeOrAbsolutePath(cwd, depFile)
|
|
563
|
+
|
|
564
|
+
this.#collectVarsDefinitions(
|
|
565
|
+
depData.vars,
|
|
566
|
+
definedVars,
|
|
567
|
+
"",
|
|
568
|
+
relativeDependencyPath
|
|
569
|
+
)
|
|
637
570
|
}
|
|
571
|
+
|
|
572
|
+
// Find variable usage in colors, tokenColors, and semanticTokenColors sections
|
|
573
|
+
this.#findVariableUsage(depData
|
|
574
|
+
?.get(LC.SECTIONS.COLORS), usedVars)
|
|
575
|
+
this.#findVariableUsage(depData
|
|
576
|
+
?.get(LC.SECTIONS.TOKEN_COLORS), usedVars)
|
|
577
|
+
this.#findVariableUsage(depData
|
|
578
|
+
?.get(LC.SECTIONS.SEMANTIC_TOKEN_COLORS), usedVars)
|
|
579
|
+
} catch {
|
|
580
|
+
// Ignore cache errors
|
|
638
581
|
}
|
|
639
582
|
}
|
|
640
583
|
|
|
@@ -642,10 +585,10 @@ export default class LintCommand extends Command {
|
|
|
642
585
|
for(const [varName, filename] of definedVars) {
|
|
643
586
|
if(!usedVars.has(varName)) {
|
|
644
587
|
issues.push({
|
|
645
|
-
type:
|
|
646
|
-
severity:
|
|
647
|
-
variable:
|
|
648
|
-
|
|
588
|
+
type: LC.ISSUE_TYPES.UNUSED_VARIABLE,
|
|
589
|
+
severity: LC.SEVERITY.LOW,
|
|
590
|
+
variable: `${LC.TEMPLATES.VARIABLE_PREFIX}${varName}`,
|
|
591
|
+
occurrence: filename,
|
|
649
592
|
})
|
|
650
593
|
}
|
|
651
594
|
}
|
|
@@ -664,18 +607,14 @@ export default class LintCommand extends Command {
|
|
|
664
607
|
* @private
|
|
665
608
|
*/
|
|
666
609
|
#collectVarsDefinitions(vars, definedVars, prefix = "", filename = "") {
|
|
667
|
-
|
|
668
|
-
return
|
|
669
|
-
|
|
670
|
-
for(const [key, value] of Object.entries(vars)) {
|
|
610
|
+
for(const [key, value] of Object.entries(vars ?? {})) {
|
|
671
611
|
const varName = prefix ? `${prefix}.${key}` : key
|
|
672
612
|
|
|
673
613
|
definedVars.set(varName, filename)
|
|
674
614
|
|
|
675
615
|
// If the value is an object, recurse for nested definitions
|
|
676
|
-
if(
|
|
616
|
+
if(typeof value === "object" && !Array.isArray(value))
|
|
677
617
|
this.#collectVarsDefinitions(value, definedVars, varName, filename)
|
|
678
|
-
}
|
|
679
618
|
}
|
|
680
619
|
}
|
|
681
620
|
|
|
@@ -689,10 +628,12 @@ export default class LintCommand extends Command {
|
|
|
689
628
|
* @private
|
|
690
629
|
*/
|
|
691
630
|
#findVariableUsage(data, usedVars) {
|
|
631
|
+
if(!data)
|
|
632
|
+
return
|
|
633
|
+
|
|
692
634
|
if(typeof data === "string") {
|
|
693
635
|
if(Evaluator.sub.test(data)) {
|
|
694
|
-
const
|
|
695
|
-
const varName = none || parens || braces
|
|
636
|
+
const varName = Evaluator.extractVariableName(data)
|
|
696
637
|
|
|
697
638
|
if(varName) {
|
|
698
639
|
usedVars.add(varName)
|
|
@@ -700,7 +641,7 @@ export default class LintCommand extends Command {
|
|
|
700
641
|
}
|
|
701
642
|
} else if(Array.isArray(data)) {
|
|
702
643
|
data.forEach(item => this.#findVariableUsage(item, usedVars))
|
|
703
|
-
} else if(
|
|
644
|
+
} else if(typeof data === "object") {
|
|
704
645
|
Object.values(data).forEach(
|
|
705
646
|
value => this.#findVariableUsage(value, usedVars)
|
|
706
647
|
)
|
|
@@ -732,12 +673,14 @@ export default class LintCommand extends Command {
|
|
|
732
673
|
allScopes.push({
|
|
733
674
|
scope,
|
|
734
675
|
index: index + 1,
|
|
735
|
-
name: entry.name ||
|
|
676
|
+
name: entry.name || LC.TEMPLATES.ENTRY_NAME(index),
|
|
736
677
|
entry
|
|
737
678
|
})
|
|
738
679
|
})
|
|
739
680
|
})
|
|
740
681
|
|
|
682
|
+
const {LOW,HIGH} = LC.SEVERITY
|
|
683
|
+
|
|
741
684
|
// Check each scope against all later scopes
|
|
742
685
|
for(let i = 0; i < allScopes.length; i++) {
|
|
743
686
|
const current = allScopes[i]
|
|
@@ -749,8 +692,8 @@ export default class LintCommand extends Command {
|
|
|
749
692
|
// This means the broad scope will mask the specific scope
|
|
750
693
|
if(this.#isBroaderScope(current.scope, later.scope)) {
|
|
751
694
|
issues.push({
|
|
752
|
-
type:
|
|
753
|
-
severity: current.index === later.index ?
|
|
695
|
+
type: LC.ISSUE_TYPES.PRECEDENCE_ISSUE,
|
|
696
|
+
severity: current.index === later.index ? LOW : HIGH,
|
|
754
697
|
specificScope: later.scope,
|
|
755
698
|
broadScope: current.scope,
|
|
756
699
|
specificRule: later.name,
|
|
@@ -769,7 +712,7 @@ export default class LintCommand extends Command {
|
|
|
769
712
|
* Determines if one scope is broader than another.
|
|
770
713
|
*
|
|
771
714
|
* A broader scope will match the same tokens as a more specific scope, plus
|
|
772
|
-
* others.
|
|
715
|
+
* others. Uses proper TextMate scope hierarchy rules.
|
|
773
716
|
*
|
|
774
717
|
* @param {string} broadScope - The potentially broader scope
|
|
775
718
|
* @param {string} specificScope - The potentially more specific scope
|
|
@@ -777,9 +720,24 @@ export default class LintCommand extends Command {
|
|
|
777
720
|
* @private
|
|
778
721
|
*/
|
|
779
722
|
#isBroaderScope(broadScope, specificScope) {
|
|
780
|
-
//
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
723
|
+
// Scopes must be different to have a precedence relationship
|
|
724
|
+
if(broadScope === specificScope)
|
|
725
|
+
return false
|
|
726
|
+
|
|
727
|
+
// Split both scopes into segments for proper comparison
|
|
728
|
+
const broadSegments = broadScope.split(".")
|
|
729
|
+
const specificSegments = specificScope.split(".")
|
|
730
|
+
|
|
731
|
+
// A broader scope must have fewer or equal segments
|
|
732
|
+
if(broadSegments.length > specificSegments.length)
|
|
733
|
+
return false
|
|
734
|
+
|
|
735
|
+
// All segments of the broad scope must match the specific scope's prefix
|
|
736
|
+
return broadSegments.every((segment, index) =>
|
|
737
|
+
segment === specificSegments[index]
|
|
738
|
+
)
|
|
784
739
|
}
|
|
785
740
|
}
|
|
741
|
+
|
|
742
|
+
// Aliases
|
|
743
|
+
const LC = LintCommand
|
package/src/ResolveCommand.js
CHANGED
|
@@ -87,6 +87,14 @@ export default class ResolveCommand extends Command {
|
|
|
87
87
|
* @param {object} theme - The compiled theme object with pool
|
|
88
88
|
* @param {string} colorName - The color key to resolve
|
|
89
89
|
* @returns {void}
|
|
90
|
+
* @example
|
|
91
|
+
* // Resolve a color variable from a compiled theme
|
|
92
|
+
* await resolveCommand.resolveColor(theme, 'colors.primary');
|
|
93
|
+
* // Output:
|
|
94
|
+
* // colors.primary:
|
|
95
|
+
* // $(vars.accent)
|
|
96
|
+
* // → #3366cc
|
|
97
|
+
* // Resolution: #3366cc
|
|
90
98
|
*/
|
|
91
99
|
async resolveColor(theme, colorName) {
|
|
92
100
|
const pool = theme.getPool()
|
|
@@ -243,6 +251,7 @@ export default class ResolveCommand extends Command {
|
|
|
243
251
|
|
|
244
252
|
// Temporarily replace tokenColors with semanticTokenColors for resolution
|
|
245
253
|
const themeOutput = theme.getOutput()
|
|
254
|
+
|
|
246
255
|
if(themeOutput?.semanticTokenColors) {
|
|
247
256
|
themeOutput.tokenColors = themeOutput.semanticTokenColors
|
|
248
257
|
}
|
|
@@ -426,7 +435,12 @@ export default class ResolveCommand extends Command {
|
|
|
426
435
|
}
|
|
427
436
|
|
|
428
437
|
if(this.#func.test(value)) {
|
|
429
|
-
const
|
|
438
|
+
const result = Evaluator.extractFunctionCall(value)
|
|
439
|
+
|
|
440
|
+
if(!result)
|
|
441
|
+
return [c`{leaf}${value}{/}`, "literal"]
|
|
442
|
+
|
|
443
|
+
const {func, args} = result
|
|
430
444
|
|
|
431
445
|
return [
|
|
432
446
|
c`{func}${func}{/}{parens}${"("}{/}{leaf}${args}{/}{parens}${")"}{/}`,
|
|
@@ -435,9 +449,9 @@ export default class ResolveCommand extends Command {
|
|
|
435
449
|
}
|
|
436
450
|
|
|
437
451
|
if(this.#sub.test(value)) {
|
|
452
|
+
const varValue = Evaluator.extractVariableName(value) || value
|
|
438
453
|
const {parens,none,braces} = Evaluator.sub.exec(value)?.groups || {}
|
|
439
454
|
const style = (braces && ["{","}"]) || (parens && ["(",")"]) || (none && ["",""])
|
|
440
|
-
const varValue = braces || parens || none || value
|
|
441
455
|
|
|
442
456
|
return [
|
|
443
457
|
c`{func}{/}{parens}${style[0]}{/}{leaf}${varValue}{/}{parens}${style[1]}{/}`,
|
package/src/Session.js
CHANGED
|
@@ -481,7 +481,9 @@ export default class Session {
|
|
|
481
481
|
if(this.#watcher)
|
|
482
482
|
await this.#watcher.close()
|
|
483
483
|
|
|
484
|
-
const dependencies = Array.from(this.#theme
|
|
484
|
+
const dependencies = Array.from(this.#theme
|
|
485
|
+
.getDependencies())
|
|
486
|
+
.map(d => d.getSourceFile().path)
|
|
485
487
|
|
|
486
488
|
this.#watcher = chokidar.watch(dependencies, {
|
|
487
489
|
// Prevent watching own output files
|
package/src/Theme.js
CHANGED
|
@@ -21,6 +21,7 @@ import FileObject from "./FileObject.js"
|
|
|
21
21
|
import Term from "./Term.js"
|
|
22
22
|
import ThemePool from "./ThemePool.js"
|
|
23
23
|
import Util from "./Util.js"
|
|
24
|
+
import Cache from "./Cache.js"
|
|
24
25
|
|
|
25
26
|
const outputFileExtension = "color-theme.json"
|
|
26
27
|
const obviouslyASentinelYouCantMissSoShutUpAboutIt = "kakadoodoo"
|
|
@@ -113,7 +114,7 @@ export default class Theme {
|
|
|
113
114
|
* Gets a specific compilation option.
|
|
114
115
|
*
|
|
115
116
|
* @param {string} option - The option name to retrieve
|
|
116
|
-
* @returns {
|
|
117
|
+
* @returns {unknown} The option value or undefined if not set
|
|
117
118
|
*/
|
|
118
119
|
getOption(option) {
|
|
119
120
|
return this.#options?.[option] ?? undefined
|
|
@@ -282,9 +283,9 @@ export default class Theme {
|
|
|
282
283
|
}
|
|
283
284
|
|
|
284
285
|
/**
|
|
285
|
-
* Gets the
|
|
286
|
+
* Gets the set of file dependencies.
|
|
286
287
|
*
|
|
287
|
-
* @returns {Set<Dependency>}
|
|
288
|
+
* @returns {Set<Dependency>} Set of dependency files
|
|
288
289
|
*/
|
|
289
290
|
getDependencies() {
|
|
290
291
|
return this.#dependencies
|
|
@@ -306,6 +307,11 @@ export default class Theme {
|
|
|
306
307
|
return this
|
|
307
308
|
}
|
|
308
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Checks if the theme has any dependencies.
|
|
312
|
+
*
|
|
313
|
+
* @returns {boolean} True if theme has dependencies
|
|
314
|
+
*/
|
|
309
315
|
hasDependencies() {
|
|
310
316
|
return this.#dependencies.size > 0
|
|
311
317
|
}
|
|
@@ -484,7 +490,7 @@ export default class Theme {
|
|
|
484
490
|
|
|
485
491
|
this.#source = source
|
|
486
492
|
|
|
487
|
-
this.addDependency(this.#sourceFile, this.#source)
|
|
493
|
+
this.addDependency(this.#sourceFile, new Map(Object.entries(this.#source)))
|
|
488
494
|
|
|
489
495
|
return this
|
|
490
496
|
}
|
|
@@ -542,6 +548,10 @@ export default class Theme {
|
|
|
542
548
|
}
|
|
543
549
|
}
|
|
544
550
|
|
|
551
|
+
/**
|
|
552
|
+
* Dependency class represents a theme file dependency.
|
|
553
|
+
* Manages the relationship between a file reference and its parsed source data.
|
|
554
|
+
*/
|
|
545
555
|
export class Dependency {
|
|
546
556
|
#sourceFile = null
|
|
547
557
|
#source = null
|
|
@@ -581,6 +591,11 @@ export class Dependency {
|
|
|
581
591
|
return this
|
|
582
592
|
}
|
|
583
593
|
|
|
594
|
+
/**
|
|
595
|
+
* Gets the parsed source data for this dependency.
|
|
596
|
+
*
|
|
597
|
+
* @returns {object|null} The parsed source data
|
|
598
|
+
*/
|
|
584
599
|
getSource() {
|
|
585
600
|
return this.#source
|
|
586
601
|
}
|
package/src/cli.js
CHANGED
|
@@ -111,9 +111,10 @@ void (async function main() {
|
|
|
111
111
|
.version(pkgJson.version)
|
|
112
112
|
|
|
113
113
|
const commands = [BuildCommand, ResolveCommand, LintCommand]
|
|
114
|
-
|
|
114
|
+
|
|
115
115
|
for(const CommandClass of commands) {
|
|
116
116
|
const command = new CommandClass({cwd, packageJson: pkgJson})
|
|
117
|
+
|
|
117
118
|
command.setCache(cache)
|
|
118
119
|
await command.buildCli(program)
|
|
119
120
|
command.addCliOptions(alwaysAvailable, false)
|