dispersa 0.4.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -39
- package/dist/android-CRDfSB3_.d.cts +126 -0
- package/dist/android-DANJjjPO.d.ts +126 -0
- package/dist/builders.cjs +220 -64
- package/dist/builders.cjs.map +1 -1
- package/dist/builders.d.cts +15 -13
- package/dist/builders.d.ts +15 -13
- package/dist/builders.js +220 -64
- package/dist/builders.js.map +1 -1
- package/dist/cli/cli.js +120 -7
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/config.d.ts +321 -0
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/index.js +119 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/dispersa-BC1kDF5u.d.ts +118 -0
- package/dist/dispersa-DL3J_Pmz.d.cts +118 -0
- package/dist/errors-qT4sJgSA.d.cts +104 -0
- package/dist/errors-qT4sJgSA.d.ts +104 -0
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +1 -83
- package/dist/errors.d.ts +1 -83
- package/dist/errors.js.map +1 -1
- package/dist/filters.cjs.map +1 -1
- package/dist/filters.d.cts +2 -2
- package/dist/filters.d.ts +2 -2
- package/dist/filters.js.map +1 -1
- package/dist/{index-CNT2Meyf.d.cts → index-Dajm5rvM.d.ts} +311 -132
- package/dist/{index-CqdaN3X0.d.ts → index-De6SjZYH.d.cts} +311 -132
- package/dist/index.cjs +813 -355
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -329
- package/dist/index.d.ts +8 -329
- package/dist/index.js +807 -355
- package/dist/index.js.map +1 -1
- package/dist/lint.cjs +1017 -0
- package/dist/lint.cjs.map +1 -0
- package/dist/lint.d.cts +463 -0
- package/dist/lint.d.ts +463 -0
- package/dist/lint.js +997 -0
- package/dist/lint.js.map +1 -0
- package/dist/preprocessors.d.cts +2 -2
- package/dist/preprocessors.d.ts +2 -2
- package/dist/renderers.cjs.map +1 -1
- package/dist/renderers.d.cts +7 -6
- package/dist/renderers.d.ts +7 -6
- package/dist/renderers.js.map +1 -1
- package/dist/transforms.cjs +0 -12
- package/dist/transforms.cjs.map +1 -1
- package/dist/transforms.d.cts +3 -7
- package/dist/transforms.d.ts +3 -7
- package/dist/transforms.js +1 -12
- package/dist/transforms.js.map +1 -1
- package/dist/{types-CZb19kiq.d.ts → types-8MLtztK3.d.ts} +56 -1
- package/dist/{types-CussyWwe.d.cts → types-BHBHRm0a.d.cts} +56 -1
- package/dist/{types-BAv39mum.d.cts → types-BltzwVYK.d.cts} +1 -1
- package/dist/{types-DWKq-eJj.d.cts → types-CAdUV-fa.d.cts} +1 -1
- package/dist/{types-CzHa7YkW.d.ts → types-DztXKlka.d.ts} +1 -1
- package/dist/{types-Bc0kA7De.d.ts → types-TQHV1MrY.d.cts} +19 -1
- package/dist/{types-Bc0kA7De.d.cts → types-TQHV1MrY.d.ts} +19 -1
- package/dist/{types-BzNcG-rI.d.ts → types-ebxDimRz.d.ts} +1 -1
- package/package.json +11 -1
package/dist/lint.cjs
ADDED
|
@@ -0,0 +1,1017 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var module$1 = require('module');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var process = require('process');
|
|
6
|
+
var jiti = require('jiti');
|
|
7
|
+
|
|
8
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
|
|
10
|
+
var process__default = /*#__PURE__*/_interopDefault(process);
|
|
11
|
+
|
|
12
|
+
// src/lint/create-rule.ts
|
|
13
|
+
function createRule(rule) {
|
|
14
|
+
return rule;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/shared/errors/index.ts
|
|
18
|
+
var DispersaError = class extends Error {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "DispersaError";
|
|
22
|
+
if (typeof Error.captureStackTrace === "function") {
|
|
23
|
+
Error.captureStackTrace(this, this.constructor);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var ConfigurationError = class extends DispersaError {
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = "ConfigurationError";
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var PluginLoader = class {
|
|
34
|
+
cwd;
|
|
35
|
+
jiti = null;
|
|
36
|
+
cache = /* @__PURE__ */ new Map();
|
|
37
|
+
constructor(options = {}) {
|
|
38
|
+
this.cwd = options.cwd ?? process__default.default.cwd();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Load a plugin from an inline object or module path
|
|
42
|
+
*
|
|
43
|
+
* @param source - Plugin object or module path string
|
|
44
|
+
* @returns Loaded plugin
|
|
45
|
+
* @throws {ConfigurationError} If plugin cannot be loaded or is invalid
|
|
46
|
+
*/
|
|
47
|
+
async load(source) {
|
|
48
|
+
if (this.isPluginObject(source)) {
|
|
49
|
+
this.validatePlugin(source);
|
|
50
|
+
return source;
|
|
51
|
+
}
|
|
52
|
+
const modulePath = source;
|
|
53
|
+
const cached = this.cache.get(modulePath);
|
|
54
|
+
if (cached) {
|
|
55
|
+
return cached;
|
|
56
|
+
}
|
|
57
|
+
const plugin = await this.loadFromModule(modulePath);
|
|
58
|
+
this.validatePlugin(plugin);
|
|
59
|
+
this.cache.set(modulePath, plugin);
|
|
60
|
+
return plugin;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Load multiple plugins
|
|
64
|
+
*
|
|
65
|
+
* @param plugins - Record of namespace to plugin source
|
|
66
|
+
* @returns Record of namespace to loaded plugin
|
|
67
|
+
*/
|
|
68
|
+
async loadAll(plugins) {
|
|
69
|
+
const entries = Object.entries(plugins);
|
|
70
|
+
const loaded = await Promise.all(
|
|
71
|
+
entries.map(async ([namespace, source]) => [namespace, await this.load(source)])
|
|
72
|
+
);
|
|
73
|
+
return Object.fromEntries(loaded);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if source is an inline plugin object
|
|
77
|
+
*/
|
|
78
|
+
isPluginObject(source) {
|
|
79
|
+
return typeof source !== "string";
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Load a plugin from a module path
|
|
83
|
+
*/
|
|
84
|
+
async loadFromModule(modulePath) {
|
|
85
|
+
const resolvedPath = path.isAbsolute(modulePath) ? modulePath : path.resolve(this.cwd, modulePath);
|
|
86
|
+
const isFilePath = modulePath.startsWith("./") || modulePath.startsWith("../") || path.isAbsolute(modulePath);
|
|
87
|
+
try {
|
|
88
|
+
if (isFilePath) {
|
|
89
|
+
return await this.loadFromFile(resolvedPath);
|
|
90
|
+
}
|
|
91
|
+
return await this.loadFromPackage(modulePath);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
94
|
+
throw new ConfigurationError(`Failed to load lint plugin '${modulePath}': ${message}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Load a plugin from a file path using jiti (supports TypeScript)
|
|
99
|
+
*/
|
|
100
|
+
async loadFromFile(filePath) {
|
|
101
|
+
this.jiti ??= jiti.createJiti(this.cwd, {
|
|
102
|
+
interopDefault: true
|
|
103
|
+
});
|
|
104
|
+
const loaded = await this.jiti(filePath);
|
|
105
|
+
const plugin = this.extractPlugin(loaded);
|
|
106
|
+
if (!plugin) {
|
|
107
|
+
throw new ConfigurationError(`Plugin file '${filePath}' does not export a valid LintPlugin`);
|
|
108
|
+
}
|
|
109
|
+
return plugin;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Load a plugin from a package name
|
|
113
|
+
*/
|
|
114
|
+
async loadFromPackage(packageName) {
|
|
115
|
+
const require2 = module$1.createRequire(this.cwd);
|
|
116
|
+
let resolvedPath;
|
|
117
|
+
try {
|
|
118
|
+
resolvedPath = require2.resolve(packageName, { paths: [this.cwd] });
|
|
119
|
+
} catch {
|
|
120
|
+
try {
|
|
121
|
+
resolvedPath = require2.resolve(packageName);
|
|
122
|
+
} catch {
|
|
123
|
+
throw new ConfigurationError(
|
|
124
|
+
`Cannot find package '${packageName}'. Make sure it is installed.`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
this.jiti ??= jiti.createJiti(this.cwd, {
|
|
129
|
+
interopDefault: true
|
|
130
|
+
});
|
|
131
|
+
const loaded = await this.jiti(resolvedPath);
|
|
132
|
+
const plugin = this.extractPlugin(loaded);
|
|
133
|
+
if (!plugin) {
|
|
134
|
+
throw new ConfigurationError(`Package '${packageName}' does not export a valid LintPlugin`);
|
|
135
|
+
}
|
|
136
|
+
return plugin;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Extract plugin from loaded module
|
|
140
|
+
*
|
|
141
|
+
* Supports multiple export patterns:
|
|
142
|
+
* - export default plugin
|
|
143
|
+
* - export const plugin = {...}
|
|
144
|
+
* - module.exports = plugin (CJS)
|
|
145
|
+
*/
|
|
146
|
+
extractPlugin(loaded) {
|
|
147
|
+
if (!loaded || typeof loaded !== "object") {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const module = loaded;
|
|
151
|
+
if (module.default && this.isValidPluginStructure(module.default)) {
|
|
152
|
+
return module.default;
|
|
153
|
+
}
|
|
154
|
+
if (module.plugin && this.isValidPluginStructure(module.plugin)) {
|
|
155
|
+
return module.plugin;
|
|
156
|
+
}
|
|
157
|
+
if (this.isValidPluginStructure(loaded)) {
|
|
158
|
+
return loaded;
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Check if object has required plugin structure
|
|
164
|
+
*/
|
|
165
|
+
isValidPluginStructure(obj) {
|
|
166
|
+
if (!obj || typeof obj !== "object") {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
const plugin = obj;
|
|
170
|
+
const rules = plugin.rules;
|
|
171
|
+
if (plugin.meta === void 0 || typeof plugin.meta !== "object" || !plugin.meta.name || rules === void 0 || Object.keys(rules).length === 0) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Validate a loaded plugin
|
|
178
|
+
*/
|
|
179
|
+
validatePlugin(plugin) {
|
|
180
|
+
if (!plugin.meta) {
|
|
181
|
+
throw new ConfigurationError("Lint plugin must have a meta property with name");
|
|
182
|
+
}
|
|
183
|
+
if (!plugin.meta.name) {
|
|
184
|
+
throw new ConfigurationError("Lint plugin meta.name is required");
|
|
185
|
+
}
|
|
186
|
+
if (!plugin.rules || typeof plugin.rules !== "object" || Object.keys(plugin.rules).length === 0) {
|
|
187
|
+
throw new ConfigurationError(
|
|
188
|
+
`Lint plugin '${plugin.meta.name}' must have a non-empty rules object`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
for (const [ruleName, rule] of Object.entries(plugin.rules)) {
|
|
192
|
+
if (!rule.meta) {
|
|
193
|
+
throw new ConfigurationError(
|
|
194
|
+
`Rule '${ruleName}' in plugin '${plugin.meta.name}' is missing meta property`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
if (!rule.meta.messages || typeof rule.meta.messages !== "object") {
|
|
198
|
+
throw new ConfigurationError(
|
|
199
|
+
`Rule '${ruleName}' in plugin '${plugin.meta.name}' is missing meta.messages`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (typeof rule.create !== "function") {
|
|
203
|
+
throw new ConfigurationError(
|
|
204
|
+
`Rule '${ruleName}' in plugin '${plugin.meta.name}' is missing create function`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Clear the plugin cache
|
|
211
|
+
*/
|
|
212
|
+
clearCache() {
|
|
213
|
+
this.cache.clear();
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// src/lint/lint-runner.ts
|
|
218
|
+
var LintRunner = class {
|
|
219
|
+
config;
|
|
220
|
+
pluginLoader;
|
|
221
|
+
resolvedConfig = null;
|
|
222
|
+
warn;
|
|
223
|
+
constructor(config) {
|
|
224
|
+
this.config = config;
|
|
225
|
+
this.pluginLoader = new PluginLoader();
|
|
226
|
+
this.warn = config.onWarn ?? console.warn;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Run all configured rules against the provided tokens
|
|
230
|
+
*
|
|
231
|
+
* Rules are executed in parallel for performance. Issues are collected
|
|
232
|
+
* and returned with counts by severity.
|
|
233
|
+
*
|
|
234
|
+
* @param tokens - Resolved tokens to lint
|
|
235
|
+
* @returns Lint result with issues and counts
|
|
236
|
+
*/
|
|
237
|
+
async run(tokens) {
|
|
238
|
+
this.resolvedConfig ??= await this.resolveConfig();
|
|
239
|
+
const { rules, plugins } = this.resolvedConfig;
|
|
240
|
+
const issues = [];
|
|
241
|
+
if (Object.keys(rules).length === 0) {
|
|
242
|
+
return { issues: [], errorCount: 0, warningCount: 0 };
|
|
243
|
+
}
|
|
244
|
+
const rulePromises = Object.entries(rules).map(async ([ruleId, ruleConfig]) => {
|
|
245
|
+
const { severity, options } = ruleConfig;
|
|
246
|
+
const rule = this.resolveRule(ruleId, plugins);
|
|
247
|
+
if (!rule) {
|
|
248
|
+
this.warn(`[lint] Unknown rule '${ruleId}' - no plugin provides this rule`);
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
const reports = [];
|
|
252
|
+
const applicableTokens = this.filterTokensByAppliesTo(tokens, rule.meta.appliesTo);
|
|
253
|
+
const mergedOptions = rule.defaultOptions ? { ...rule.defaultOptions, ...options } : options;
|
|
254
|
+
const context = {
|
|
255
|
+
id: ruleId,
|
|
256
|
+
options: mergedOptions,
|
|
257
|
+
tokens: applicableTokens,
|
|
258
|
+
report: (descriptor) => {
|
|
259
|
+
reports.push(descriptor);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
try {
|
|
263
|
+
await rule.create(context);
|
|
264
|
+
} catch (error) {
|
|
265
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
266
|
+
return [
|
|
267
|
+
{
|
|
268
|
+
ruleId: "lint/rule-error",
|
|
269
|
+
severity: "error",
|
|
270
|
+
message: `Rule '${ruleId}' failed: ${message}`,
|
|
271
|
+
tokenName: "(rule execution)",
|
|
272
|
+
tokenPath: []
|
|
273
|
+
}
|
|
274
|
+
];
|
|
275
|
+
}
|
|
276
|
+
return reports.map((report) => {
|
|
277
|
+
const messageTemplate = rule.meta.messages[report.messageId];
|
|
278
|
+
const message = messageTemplate ? this.interpolateMessage(messageTemplate, report.data) : report.messageId;
|
|
279
|
+
return {
|
|
280
|
+
ruleId,
|
|
281
|
+
severity,
|
|
282
|
+
message,
|
|
283
|
+
tokenName: report.token.name,
|
|
284
|
+
tokenPath: report.token.path
|
|
285
|
+
};
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
const allIssues = await Promise.all(rulePromises);
|
|
289
|
+
issues.push(...allIssues.flat());
|
|
290
|
+
const errorCount = issues.filter((i) => i.severity === "error").length;
|
|
291
|
+
const warningCount = issues.filter((i) => i.severity === "warn").length;
|
|
292
|
+
return { issues, errorCount, warningCount };
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Resolve configuration: load plugins, parse rule configs
|
|
296
|
+
*/
|
|
297
|
+
async resolveConfig() {
|
|
298
|
+
const { plugins: pluginSources = {}, rules: ruleConfigs = {} } = this.config;
|
|
299
|
+
const plugins = await this.pluginLoader.loadAll(pluginSources);
|
|
300
|
+
const rules = {};
|
|
301
|
+
for (const [ruleId, config] of Object.entries(ruleConfigs)) {
|
|
302
|
+
const resolved = this.resolveRuleConfig(config);
|
|
303
|
+
if (resolved) {
|
|
304
|
+
rules[ruleId] = resolved;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
enabled: true,
|
|
309
|
+
failOnError: this.config.failOnError ?? true,
|
|
310
|
+
plugins,
|
|
311
|
+
rules
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
filterTokensByAppliesTo(tokens, appliesTo) {
|
|
315
|
+
if (!appliesTo || appliesTo === "all") {
|
|
316
|
+
return tokens;
|
|
317
|
+
}
|
|
318
|
+
const filtered = {};
|
|
319
|
+
for (const [name, token] of Object.entries(tokens)) {
|
|
320
|
+
if (token.$type && appliesTo.includes(token.$type)) {
|
|
321
|
+
filtered[name] = token;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return filtered;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Parse rule configuration into resolved format
|
|
328
|
+
*/
|
|
329
|
+
resolveRuleConfig(config) {
|
|
330
|
+
if (typeof config === "string") {
|
|
331
|
+
if (config === "off") {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
return { severity: config, options: {} };
|
|
335
|
+
}
|
|
336
|
+
const [severity, options = {}] = config;
|
|
337
|
+
if (severity === "off") {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
return { severity, options };
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Resolve a rule from plugins by rule ID
|
|
344
|
+
*
|
|
345
|
+
* Rule IDs are formatted as 'namespace/rule-name'
|
|
346
|
+
*/
|
|
347
|
+
resolveRule(ruleId, plugins) {
|
|
348
|
+
const separatorIndex = ruleId.indexOf("/");
|
|
349
|
+
if (separatorIndex === -1) {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
const namespace = ruleId.slice(0, separatorIndex);
|
|
353
|
+
const ruleName = ruleId.slice(separatorIndex + 1);
|
|
354
|
+
const plugin = plugins[namespace];
|
|
355
|
+
if (!plugin) {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
return plugin.rules[ruleName] ?? null;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Interpolate message template with data
|
|
362
|
+
*
|
|
363
|
+
* Replaces {{key}} placeholders with values from data
|
|
364
|
+
*/
|
|
365
|
+
interpolateMessage(template, data) {
|
|
366
|
+
if (!data) {
|
|
367
|
+
return template;
|
|
368
|
+
}
|
|
369
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
370
|
+
const value = data[key];
|
|
371
|
+
return value !== void 0 ? String(value) : `{{${key}}}`;
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Clear the plugin cache
|
|
376
|
+
*/
|
|
377
|
+
clearCache() {
|
|
378
|
+
this.pluginLoader.clearCache();
|
|
379
|
+
this.resolvedConfig = null;
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// src/lint/utils/glob-matcher.ts
|
|
384
|
+
var MAX_CACHE_SIZE = 1e3;
|
|
385
|
+
var cache = /* @__PURE__ */ new Map();
|
|
386
|
+
function globToRegex(pattern) {
|
|
387
|
+
const cached = cache.get(pattern);
|
|
388
|
+
if (cached) {
|
|
389
|
+
cache.delete(pattern);
|
|
390
|
+
cache.set(pattern, cached);
|
|
391
|
+
return cached;
|
|
392
|
+
}
|
|
393
|
+
const regex = new RegExp(
|
|
394
|
+
"^" + pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*") + "$"
|
|
395
|
+
);
|
|
396
|
+
if (cache.size >= MAX_CACHE_SIZE) {
|
|
397
|
+
const oldest = cache.keys().next().value;
|
|
398
|
+
if (oldest !== void 0) {
|
|
399
|
+
cache.delete(oldest);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
cache.set(pattern, regex);
|
|
403
|
+
return regex;
|
|
404
|
+
}
|
|
405
|
+
function matchesGlob(name, patterns) {
|
|
406
|
+
return patterns.some((pattern) => globToRegex(pattern).test(name));
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// src/lint/utils/reference-extractor.ts
|
|
410
|
+
var ALIAS_PATTERN = /\{([^}]+)\}/g;
|
|
411
|
+
function extractReferences(value) {
|
|
412
|
+
const refs = [];
|
|
413
|
+
if (typeof value === "string") {
|
|
414
|
+
let match;
|
|
415
|
+
ALIAS_PATTERN.lastIndex = 0;
|
|
416
|
+
while ((match = ALIAS_PATTERN.exec(value)) !== null) {
|
|
417
|
+
if (match[1]) {
|
|
418
|
+
refs.push(match[1]);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
} else if (Array.isArray(value)) {
|
|
422
|
+
for (const item of value) {
|
|
423
|
+
refs.push(...extractReferences(item));
|
|
424
|
+
}
|
|
425
|
+
} else if (typeof value === "object" && value !== null) {
|
|
426
|
+
for (const v of Object.values(value)) {
|
|
427
|
+
refs.push(...extractReferences(v));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return refs;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// src/lint/rules/naming-convention.ts
|
|
434
|
+
var PATTERNS = {
|
|
435
|
+
"kebab-case": /^([a-z][a-z0-9]*)(-[a-z0-9]+)*$/,
|
|
436
|
+
camelCase: /^[a-z][a-zA-Z0-9]*$/,
|
|
437
|
+
PascalCase: /^[A-Z][a-zA-Z0-9]*$/,
|
|
438
|
+
snake_case: /^([a-z][a-z0-9]*)(_[a-z0-9]+)*$/,
|
|
439
|
+
"screaming-snake": /^([A-Z][A-Z0-9]*)(_[A-Z0-9]+)*$/
|
|
440
|
+
};
|
|
441
|
+
var DEFAULT_PATTERN = /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/;
|
|
442
|
+
var namingConvention = createRule({
|
|
443
|
+
meta: {
|
|
444
|
+
name: "naming-convention",
|
|
445
|
+
description: "Enforce consistent token naming conventions",
|
|
446
|
+
messages: {
|
|
447
|
+
INVALID_FORMAT: "Token '{{name}}' does not match '{{format}}' format",
|
|
448
|
+
INVALID_SEGMENT: "Segment '{{segment}}' in token '{{name}}' does not match '{{format}}' format"
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
defaultOptions: { format: "kebab-case", allowNumericSegments: true },
|
|
452
|
+
create({ tokens, options, report }) {
|
|
453
|
+
const format = options.format ?? "kebab-case";
|
|
454
|
+
const ignore = options.ignore ?? [];
|
|
455
|
+
const customPattern = options.pattern;
|
|
456
|
+
const allowNumericSegments = options.allowNumericSegments ?? true;
|
|
457
|
+
let segmentPattern;
|
|
458
|
+
if (customPattern) {
|
|
459
|
+
segmentPattern = new RegExp(customPattern);
|
|
460
|
+
} else {
|
|
461
|
+
segmentPattern = PATTERNS[format] ?? DEFAULT_PATTERN;
|
|
462
|
+
}
|
|
463
|
+
const numericPattern = /^\d+$/;
|
|
464
|
+
for (const token of Object.values(tokens)) {
|
|
465
|
+
if (ignore.length > 0 && matchesGlob(token.name, ignore)) {
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const segments = token.path;
|
|
469
|
+
let hasError = false;
|
|
470
|
+
for (const segment of segments) {
|
|
471
|
+
if (allowNumericSegments && numericPattern.test(segment)) {
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
if (!segmentPattern.test(segment)) {
|
|
475
|
+
report({
|
|
476
|
+
token,
|
|
477
|
+
messageId: "INVALID_SEGMENT",
|
|
478
|
+
data: { name: token.name, segment, format: customPattern ?? format }
|
|
479
|
+
});
|
|
480
|
+
hasError = true;
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (!hasError && customPattern && !segmentPattern.test(token.name)) {
|
|
485
|
+
report({
|
|
486
|
+
token,
|
|
487
|
+
messageId: "INVALID_FORMAT",
|
|
488
|
+
data: { name: token.name, format: customPattern }
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// src/lint/rules/no-deprecated-usage.ts
|
|
496
|
+
var noDeprecatedUsage = createRule({
|
|
497
|
+
meta: {
|
|
498
|
+
name: "no-deprecated-usage",
|
|
499
|
+
description: "Disallow references to deprecated tokens",
|
|
500
|
+
messages: {
|
|
501
|
+
REFERENCES_DEPRECATED: "Token '{{name}}' references deprecated token '{{ref}}'. {{reason}}"
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
defaultOptions: {},
|
|
505
|
+
create({ tokens, options, report }) {
|
|
506
|
+
const ignore = options.ignore ?? [];
|
|
507
|
+
const deprecatedTokens = /* @__PURE__ */ new Map();
|
|
508
|
+
for (const token of Object.values(tokens)) {
|
|
509
|
+
if (token.$deprecated) {
|
|
510
|
+
const reason = typeof token.$deprecated === "string" ? token.$deprecated : "";
|
|
511
|
+
deprecatedTokens.set(token.name, reason || true);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (deprecatedTokens.size === 0) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
for (const token of Object.values(tokens)) {
|
|
518
|
+
if (ignore.length > 0 && matchesGlob(token.name, ignore)) {
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
if (deprecatedTokens.has(token.name)) {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
const refs = extractReferences(token.originalValue);
|
|
525
|
+
for (const ref of refs) {
|
|
526
|
+
const deprecation = deprecatedTokens.get(ref);
|
|
527
|
+
if (deprecation) {
|
|
528
|
+
const reason = deprecation === true ? "" : `(${deprecation})`;
|
|
529
|
+
report({
|
|
530
|
+
token,
|
|
531
|
+
messageId: "REFERENCES_DEPRECATED",
|
|
532
|
+
data: { name: token.name, ref, reason }
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// src/lint/rules/no-duplicate-values.ts
|
|
541
|
+
function valueKey(value) {
|
|
542
|
+
if (value === null) {
|
|
543
|
+
return "null";
|
|
544
|
+
}
|
|
545
|
+
if (value === void 0) {
|
|
546
|
+
return "undefined";
|
|
547
|
+
}
|
|
548
|
+
if (typeof value === "object") {
|
|
549
|
+
return JSON.stringify(sortKeys(value));
|
|
550
|
+
}
|
|
551
|
+
return String(value);
|
|
552
|
+
}
|
|
553
|
+
function sortKeys(obj) {
|
|
554
|
+
if (Array.isArray(obj)) {
|
|
555
|
+
return obj.map(sortKeys);
|
|
556
|
+
}
|
|
557
|
+
if (typeof obj === "object" && obj !== null) {
|
|
558
|
+
const sorted = {};
|
|
559
|
+
for (const key of Object.keys(obj).sort()) {
|
|
560
|
+
sorted[key] = sortKeys(obj[key]);
|
|
561
|
+
}
|
|
562
|
+
return sorted;
|
|
563
|
+
}
|
|
564
|
+
return obj;
|
|
565
|
+
}
|
|
566
|
+
var noDuplicateValues = createRule({
|
|
567
|
+
meta: {
|
|
568
|
+
name: "no-duplicate-values",
|
|
569
|
+
description: "Detect tokens with duplicate values (excluding aliases)",
|
|
570
|
+
messages: {
|
|
571
|
+
DUPLICATE_VALUE: "Token '{{name}}' has the same value as '{{duplicate}}'. Consider using an alias instead."
|
|
572
|
+
}
|
|
573
|
+
},
|
|
574
|
+
defaultOptions: {},
|
|
575
|
+
create({ tokens, options, report }) {
|
|
576
|
+
const ignore = options.ignore ?? [];
|
|
577
|
+
const types = options.types;
|
|
578
|
+
const valueMap = /* @__PURE__ */ new Map();
|
|
579
|
+
for (const token of Object.values(tokens)) {
|
|
580
|
+
if (ignore.length > 0 && matchesGlob(token.name, ignore)) {
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
if (token._isAlias) {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
if (types && types.length > 0 && !types.includes(token.$type ?? "")) {
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
const key = valueKey(token.$value);
|
|
590
|
+
const existing = valueMap.get(key);
|
|
591
|
+
if (existing) {
|
|
592
|
+
existing.push(token);
|
|
593
|
+
} else {
|
|
594
|
+
valueMap.set(key, [token]);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
for (const tokenList of valueMap.values()) {
|
|
598
|
+
if (tokenList.length > 1) {
|
|
599
|
+
const first = tokenList[0];
|
|
600
|
+
if (!first) {
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
for (let i = 1; i < tokenList.length; i++) {
|
|
604
|
+
const current = tokenList[i];
|
|
605
|
+
if (current) {
|
|
606
|
+
report({
|
|
607
|
+
token: current,
|
|
608
|
+
messageId: "DUPLICATE_VALUE",
|
|
609
|
+
data: { name: current.name, duplicate: first.name }
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
// src/lint/rules/path-schema/matcher.ts
|
|
619
|
+
var PathSchemaMatcher = class {
|
|
620
|
+
segments;
|
|
621
|
+
pathPatterns;
|
|
622
|
+
transitionRules;
|
|
623
|
+
constructor(config) {
|
|
624
|
+
this.segments = config.segments ?? {};
|
|
625
|
+
this.pathPatterns = this.compilePaths(config.paths ?? [], this.segments);
|
|
626
|
+
this.transitionRules = this.compileTransitions(config.transitions ?? []);
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Validate a token against the schema
|
|
630
|
+
*/
|
|
631
|
+
validate(token) {
|
|
632
|
+
const violations = [];
|
|
633
|
+
const pathSegments = token.path;
|
|
634
|
+
const hasPaths = this.pathPatterns.length > 0;
|
|
635
|
+
const hasTransitions = this.transitionRules.length > 0;
|
|
636
|
+
if (hasTransitions) {
|
|
637
|
+
const transitionViolations = this.validateTransitions(pathSegments, token.name);
|
|
638
|
+
violations.push(...transitionViolations);
|
|
639
|
+
}
|
|
640
|
+
if (hasPaths) {
|
|
641
|
+
const matchesAny = this.pathPatterns.some((p) => this.matchPattern(p, pathSegments));
|
|
642
|
+
if (!matchesAny) {
|
|
643
|
+
violations.push({
|
|
644
|
+
type: "INVALID_PATH",
|
|
645
|
+
data: { path: token.name }
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return violations;
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Validate transitions between segments.
|
|
653
|
+
*
|
|
654
|
+
* Deny rules are checked independently (any match = violation).
|
|
655
|
+
* Allow rules use OR semantics: at least one must match.
|
|
656
|
+
*/
|
|
657
|
+
validateTransitions(segments, tokenName) {
|
|
658
|
+
const violations = [];
|
|
659
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
660
|
+
const from = segments[i];
|
|
661
|
+
const to = segments[i + 1];
|
|
662
|
+
if (!from || !to) {
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
const applicableRules = this.transitionRules.filter((r) => this.matchesPattern(from, r.from));
|
|
666
|
+
if (applicableRules.length === 0) {
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
const denyRules = applicableRules.filter((r) => r.allow === false);
|
|
670
|
+
const allowRules = applicableRules.filter((r) => r.allow !== false);
|
|
671
|
+
for (const rule of denyRules) {
|
|
672
|
+
if (this.matchesPattern(to, rule.to)) {
|
|
673
|
+
violations.push({
|
|
674
|
+
type: "FORBIDDEN_TRANSITION",
|
|
675
|
+
data: { from, to, path: tokenName }
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (allowRules.length > 0) {
|
|
680
|
+
const anyAllowMatches = allowRules.some((r) => this.matchesPattern(to, r.to));
|
|
681
|
+
if (!anyAllowMatches) {
|
|
682
|
+
violations.push({
|
|
683
|
+
type: "FORBIDDEN_TRANSITION",
|
|
684
|
+
data: { from, to, path: tokenName }
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
return violations;
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Check if a value matches a pattern
|
|
693
|
+
*/
|
|
694
|
+
matchesPattern(value, pattern) {
|
|
695
|
+
if (typeof pattern === "string") {
|
|
696
|
+
return value === pattern;
|
|
697
|
+
}
|
|
698
|
+
if (Array.isArray(pattern)) {
|
|
699
|
+
return pattern.includes(value);
|
|
700
|
+
}
|
|
701
|
+
return pattern.test(value);
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Compile path patterns into matcher structures
|
|
705
|
+
*/
|
|
706
|
+
compilePaths(patterns, segments) {
|
|
707
|
+
return patterns.map((p) => this.parsePattern(p, segments));
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Parse a path pattern string into compiled form
|
|
711
|
+
* - `{name}` is a segment placeholder
|
|
712
|
+
* - `*` is a wildcard that matches any single segment
|
|
713
|
+
* - `.` is the path separator (implicit between segments)
|
|
714
|
+
*/
|
|
715
|
+
parsePattern(pattern, _segments) {
|
|
716
|
+
const parts = [];
|
|
717
|
+
const regex = /\{(\w+)\}|(\*)|([^{}*]+)/g;
|
|
718
|
+
let match;
|
|
719
|
+
while ((match = regex.exec(pattern)) !== null) {
|
|
720
|
+
if (match[1]) {
|
|
721
|
+
parts.push({ type: "segment", name: match[1] });
|
|
722
|
+
} else if (match[2]) {
|
|
723
|
+
parts.push({ type: "wildcard" });
|
|
724
|
+
} else if (match[3]) {
|
|
725
|
+
parts.push({ type: "literal", value: match[3] });
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return parts;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Match path segments against a compiled pattern using dynamic programming.
|
|
732
|
+
* Supports optional segments via DP table.
|
|
733
|
+
*
|
|
734
|
+
* DP[i][j] = can we match path[0..i) with pattern[0..j)?
|
|
735
|
+
*/
|
|
736
|
+
matchPattern(pattern, pathSegments) {
|
|
737
|
+
const patternParts = pattern.filter((p) => p.type === "segment" || p.type === "wildcard");
|
|
738
|
+
const pathLen = pathSegments.length;
|
|
739
|
+
const patternLen = patternParts.length;
|
|
740
|
+
const dp = [];
|
|
741
|
+
for (let i = 0; i <= pathLen; i++) {
|
|
742
|
+
dp[i] = [];
|
|
743
|
+
for (let j = 0; j <= patternLen; j++) {
|
|
744
|
+
dp[i][j] = false;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
dp[0][0] = true;
|
|
748
|
+
for (let i = 0; i <= pathLen; i++) {
|
|
749
|
+
for (let j = 0; j <= patternLen; j++) {
|
|
750
|
+
const currentState = dp[i][j];
|
|
751
|
+
if (!currentState) {
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
if (i === pathLen) {
|
|
755
|
+
if (j < patternLen && this.isPartOptional(patternParts[j])) {
|
|
756
|
+
dp[i][j + 1] = true;
|
|
757
|
+
}
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
if (j === patternLen) {
|
|
761
|
+
if (i === pathLen) {
|
|
762
|
+
dp[i][j] = true;
|
|
763
|
+
}
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
const part = patternParts[j];
|
|
767
|
+
if (i < pathLen && this.matchPatternPart(part, pathSegments[i])) {
|
|
768
|
+
dp[i + 1][j + 1] = true;
|
|
769
|
+
}
|
|
770
|
+
if (this.isPartOptional(part)) {
|
|
771
|
+
dp[i][j + 1] = true;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return dp[pathLen][patternLen] ?? false;
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Check if a pattern part is optional based on its segment definition
|
|
779
|
+
*/
|
|
780
|
+
isPartOptional(part) {
|
|
781
|
+
if (part.type !== "segment" || !part.name) {
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
const segmentDef = this.segments[part.name];
|
|
785
|
+
return segmentDef?.optional ?? false;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Match a single pattern part against a path segment value
|
|
789
|
+
*/
|
|
790
|
+
matchPatternPart(part, value) {
|
|
791
|
+
if (part.type === "wildcard") {
|
|
792
|
+
return true;
|
|
793
|
+
}
|
|
794
|
+
if (part.type === "segment" && part.name) {
|
|
795
|
+
const segment = this.segments[part.name];
|
|
796
|
+
if (!segment) {
|
|
797
|
+
return true;
|
|
798
|
+
}
|
|
799
|
+
return this.matchesSegmentDefinition(value, segment);
|
|
800
|
+
}
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Check if a value matches a segment definition
|
|
805
|
+
*/
|
|
806
|
+
matchesSegmentDefinition(value, definition) {
|
|
807
|
+
const { values } = definition;
|
|
808
|
+
if (Array.isArray(values)) {
|
|
809
|
+
return values.some((v) => typeof v === "string" ? v === value : v.test(value));
|
|
810
|
+
}
|
|
811
|
+
return values.test(value);
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Compile transition rules
|
|
815
|
+
*/
|
|
816
|
+
compileTransitions(transitions) {
|
|
817
|
+
return transitions.map((t) => ({
|
|
818
|
+
from: t.from,
|
|
819
|
+
to: t.to,
|
|
820
|
+
allow: t.allow ?? true
|
|
821
|
+
}));
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
// src/lint/rules/path-schema/index.ts
|
|
826
|
+
var pathSchema = createRule({
|
|
827
|
+
meta: {
|
|
828
|
+
name: "path-schema",
|
|
829
|
+
description: "Enforce token path segment structure",
|
|
830
|
+
messages: {
|
|
831
|
+
INVALID_PATH: "Token path '{{path}}' does not match any defined pattern",
|
|
832
|
+
UNKNOWN_SEGMENT: "Segment '{{segment}}' at position {{position}} in '{{path}}' is not valid",
|
|
833
|
+
FORBIDDEN_TRANSITION: "Segment '{{to}}' cannot follow '{{from}}' in path '{{path}}'"
|
|
834
|
+
}
|
|
835
|
+
},
|
|
836
|
+
defaultOptions: {
|
|
837
|
+
segments: {},
|
|
838
|
+
paths: [],
|
|
839
|
+
transitions: []
|
|
840
|
+
},
|
|
841
|
+
create({ tokens, options, report }) {
|
|
842
|
+
const ignore = options.ignore ?? [];
|
|
843
|
+
if ((!options.paths || options.paths.length === 0) && (!options.transitions || options.transitions.length === 0)) {
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
const matcher = new PathSchemaMatcher(options);
|
|
847
|
+
for (const token of Object.values(tokens)) {
|
|
848
|
+
if (ignore.length > 0 && matchesGlob(token.name, ignore)) {
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
const violations = matcher.validate(token);
|
|
852
|
+
for (const violation of violations) {
|
|
853
|
+
report({
|
|
854
|
+
token,
|
|
855
|
+
messageId: violation.type,
|
|
856
|
+
data: violation.data
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
// src/lint/rules/require-description.ts
|
|
864
|
+
var requireDescription = createRule({
|
|
865
|
+
meta: {
|
|
866
|
+
name: "require-description",
|
|
867
|
+
description: "Require tokens to have descriptions",
|
|
868
|
+
messages: {
|
|
869
|
+
MISSING_DESCRIPTION: "Token '{{name}}' is missing a description",
|
|
870
|
+
TOO_SHORT: "Token '{{name}}' description is too short ({{length}} chars, min {{minLength}})"
|
|
871
|
+
}
|
|
872
|
+
},
|
|
873
|
+
defaultOptions: { minLength: 1 },
|
|
874
|
+
create({ tokens, options, report }) {
|
|
875
|
+
const minLength = options.minLength ?? 1;
|
|
876
|
+
const ignore = options.ignore ?? [];
|
|
877
|
+
for (const token of Object.values(tokens)) {
|
|
878
|
+
if (ignore.length > 0 && matchesGlob(token.name, ignore)) {
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
if (!token.$description) {
|
|
882
|
+
report({
|
|
883
|
+
token,
|
|
884
|
+
messageId: "MISSING_DESCRIPTION",
|
|
885
|
+
data: { name: token.name }
|
|
886
|
+
});
|
|
887
|
+
} else if (token.$description.length < minLength) {
|
|
888
|
+
report({
|
|
889
|
+
token,
|
|
890
|
+
messageId: "TOO_SHORT",
|
|
891
|
+
data: {
|
|
892
|
+
name: token.name,
|
|
893
|
+
length: token.$description.length,
|
|
894
|
+
minLength
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
// src/lint/rules/index.ts
|
|
903
|
+
function buildDispersaPlugin() {
|
|
904
|
+
const rules = {
|
|
905
|
+
"require-description": requireDescription,
|
|
906
|
+
"naming-convention": namingConvention,
|
|
907
|
+
"no-deprecated-usage": noDeprecatedUsage,
|
|
908
|
+
"no-duplicate-values": noDuplicateValues,
|
|
909
|
+
"path-schema": pathSchema
|
|
910
|
+
};
|
|
911
|
+
const plugin = { meta: { name: "dispersa" }, rules, configs: {} };
|
|
912
|
+
const recommended = {
|
|
913
|
+
plugins: { dispersa: plugin },
|
|
914
|
+
rules: {
|
|
915
|
+
"dispersa/require-description": "warn",
|
|
916
|
+
"dispersa/naming-convention": ["error", { format: "kebab-case" }],
|
|
917
|
+
"dispersa/no-deprecated-usage": "warn"
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
const strict = {
|
|
921
|
+
plugins: { dispersa: plugin },
|
|
922
|
+
rules: {
|
|
923
|
+
"dispersa/require-description": "error",
|
|
924
|
+
"dispersa/naming-convention": ["error", { format: "kebab-case" }],
|
|
925
|
+
"dispersa/no-deprecated-usage": "error",
|
|
926
|
+
"dispersa/no-duplicate-values": "error"
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
const minimal = {
|
|
930
|
+
plugins: { dispersa: plugin },
|
|
931
|
+
rules: {
|
|
932
|
+
"dispersa/no-deprecated-usage": "warn"
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
plugin.configs = { recommended, strict, minimal };
|
|
936
|
+
return { plugin, recommended, strict, minimal };
|
|
937
|
+
}
|
|
938
|
+
var {
|
|
939
|
+
plugin: dispersaPlugin,
|
|
940
|
+
recommended: recommendedConfig,
|
|
941
|
+
strict: strictConfig,
|
|
942
|
+
minimal: minimalConfig
|
|
943
|
+
} = buildDispersaPlugin();
|
|
944
|
+
|
|
945
|
+
// src/cli/formatters/lint-formatter.ts
|
|
946
|
+
var formatLintJson = (result) => {
|
|
947
|
+
return JSON.stringify(result, null, 2);
|
|
948
|
+
};
|
|
949
|
+
var formatLintStylish = (result) => {
|
|
950
|
+
const lines = [];
|
|
951
|
+
if (result.issues.length === 0) {
|
|
952
|
+
return "\u2713 No lint issues found";
|
|
953
|
+
}
|
|
954
|
+
const byToken = /* @__PURE__ */ new Map();
|
|
955
|
+
for (const issue of result.issues) {
|
|
956
|
+
const existing = byToken.get(issue.tokenName) ?? [];
|
|
957
|
+
existing.push(issue);
|
|
958
|
+
byToken.set(issue.tokenName, existing);
|
|
959
|
+
}
|
|
960
|
+
for (const [tokenName, issues] of byToken) {
|
|
961
|
+
lines.push(``);
|
|
962
|
+
lines.push(` ${tokenName}`);
|
|
963
|
+
for (const issue of issues) {
|
|
964
|
+
const severity = issue.severity === "error" ? "\u2716" : "\u26A0";
|
|
965
|
+
const label = issue.severity === "error" ? "error" : "warning";
|
|
966
|
+
lines.push(` ${severity} ${label}: ${issue.message} [${issue.ruleId}]`);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
lines.push(``);
|
|
970
|
+
if (result.errorCount > 0 || result.warningCount > 0) {
|
|
971
|
+
const parts = [];
|
|
972
|
+
if (result.errorCount > 0) {
|
|
973
|
+
parts.push(`${result.errorCount} error${result.errorCount === 1 ? "" : "s"}`);
|
|
974
|
+
}
|
|
975
|
+
if (result.warningCount > 0) {
|
|
976
|
+
parts.push(`${result.warningCount} warning${result.warningCount === 1 ? "" : "s"}`);
|
|
977
|
+
}
|
|
978
|
+
lines.push(`\u2716 ${parts.join(", ")}`);
|
|
979
|
+
}
|
|
980
|
+
return lines.join("\n");
|
|
981
|
+
};
|
|
982
|
+
var formatLintCompact = (result) => {
|
|
983
|
+
const lines = [];
|
|
984
|
+
for (const issue of result.issues) {
|
|
985
|
+
const severity = issue.severity.toUpperCase();
|
|
986
|
+
lines.push(`${severity}: ${issue.ruleId} - ${issue.message} (token: ${issue.tokenName})`);
|
|
987
|
+
}
|
|
988
|
+
if (result.errorCount > 0 || result.warningCount > 0) {
|
|
989
|
+
lines.push(`SUMMARY: ${result.errorCount} errors, ${result.warningCount} warnings`);
|
|
990
|
+
}
|
|
991
|
+
return lines.join("\n");
|
|
992
|
+
};
|
|
993
|
+
/**
|
|
994
|
+
* @license MIT
|
|
995
|
+
* Copyright (c) 2025-present Dispersa
|
|
996
|
+
*
|
|
997
|
+
* This source code is licensed under the MIT license found in the
|
|
998
|
+
* LICENSE file in the root directory of this source tree.
|
|
999
|
+
*/
|
|
1000
|
+
|
|
1001
|
+
exports.LintRunner = LintRunner;
|
|
1002
|
+
exports.PluginLoader = PluginLoader;
|
|
1003
|
+
exports.createRule = createRule;
|
|
1004
|
+
exports.dispersaPlugin = dispersaPlugin;
|
|
1005
|
+
exports.formatLintCompact = formatLintCompact;
|
|
1006
|
+
exports.formatLintJson = formatLintJson;
|
|
1007
|
+
exports.formatLintStylish = formatLintStylish;
|
|
1008
|
+
exports.minimalConfig = minimalConfig;
|
|
1009
|
+
exports.namingConvention = namingConvention;
|
|
1010
|
+
exports.noDeprecatedUsage = noDeprecatedUsage;
|
|
1011
|
+
exports.noDuplicateValues = noDuplicateValues;
|
|
1012
|
+
exports.pathSchema = pathSchema;
|
|
1013
|
+
exports.recommendedConfig = recommendedConfig;
|
|
1014
|
+
exports.requireDescription = requireDescription;
|
|
1015
|
+
exports.strictConfig = strictConfig;
|
|
1016
|
+
//# sourceMappingURL=lint.cjs.map
|
|
1017
|
+
//# sourceMappingURL=lint.cjs.map
|