@pandacss/eslint-plugin 0.3.2 → 2.0.0-beta.11

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.
@@ -0,0 +1,1261 @@
1
+ // src/rules/shared.ts
2
+ function getSourceText(context) {
3
+ return context.sourceCode?.text ?? context.sourceCode?.getText?.() ?? "";
4
+ }
5
+ function byteToIndex(text, byteOffset) {
6
+ let bytes = 0;
7
+ for (let i = 0; i < text.length; ) {
8
+ if (bytes >= byteOffset) return i;
9
+ const cp = text.codePointAt(i);
10
+ bytes += cp <= 127 ? 1 : cp <= 2047 ? 2 : cp <= 65535 ? 3 : 4;
11
+ i += cp > 65535 ? 2 : 1;
12
+ }
13
+ return text.length;
14
+ }
15
+ function toEslintLoc(range) {
16
+ return {
17
+ start: {
18
+ line: range.start.line,
19
+ column: Math.max(0, range.start.column - 1)
20
+ },
21
+ end: {
22
+ line: range.end.line,
23
+ column: Math.max(0, range.end.column - 1)
24
+ }
25
+ };
26
+ }
27
+
28
+ // src/rules/consistent-property-style.ts
29
+ var consistentPropertyStyleRuleName = "consistent-property-style";
30
+ function createConsistentPropertyStyleRule(options) {
31
+ const aliasOf = {};
32
+ for (const [alias, canonical] of Object.entries(options.shorthands)) {
33
+ const current = aliasOf[canonical];
34
+ if (current === void 0 || alias.length < current.length || alias.length === current.length && alias < current) {
35
+ aliasOf[canonical] = alias;
36
+ }
37
+ }
38
+ return {
39
+ meta: {
40
+ type: "suggestion",
41
+ docs: {
42
+ description: "Enforce a consistent property style: Panda shorthand aliases or longhand canonical names."
43
+ },
44
+ fixable: "code",
45
+ schema: [
46
+ {
47
+ type: "object",
48
+ properties: {
49
+ style: { enum: ["shorthand", "longhand"] },
50
+ ignore: { type: "array", items: { type: "string" }, uniqueItems: true }
51
+ },
52
+ additionalProperties: false
53
+ }
54
+ ],
55
+ messages: {
56
+ useShorthand: 'Use the shorthand "{{target}}" instead of "{{current}}".',
57
+ useLonghand: 'Use the longhand "{{target}}" instead of "{{current}}".'
58
+ }
59
+ },
60
+ create(context) {
61
+ const configured = context.options?.[0];
62
+ const style = configured?.style ?? "longhand";
63
+ const ignore = new Set(configured?.ignore ?? []);
64
+ const source = getSourceText(context);
65
+ return {
66
+ Program() {
67
+ const inspection = options.inspect(context);
68
+ if (!inspection) return;
69
+ for (const entry of inspection.styleEntries) {
70
+ if (entry.kind !== "utility") continue;
71
+ const name = entry.name;
72
+ if (ignore.has(name)) continue;
73
+ const target = style === "longhand" ? options.shorthands[name] : options.shorthands[name] === void 0 ? aliasOf[name] : void 0;
74
+ if (target === void 0 || target === name) continue;
75
+ const descriptor = {
76
+ messageId: style === "shorthand" ? "useShorthand" : "useLonghand",
77
+ data: { target, current: name },
78
+ loc: toEslintLoc(entry.range)
79
+ };
80
+ if (entry.fixable === "safe" && entry.keySpan && source) {
81
+ const range = [
82
+ byteToIndex(source, entry.keySpan.start),
83
+ byteToIndex(source, entry.keySpan.end)
84
+ ];
85
+ descriptor.fix = (fixer) => fixer.replaceTextRange(range, target);
86
+ }
87
+ context.report(descriptor);
88
+ }
89
+ }
90
+ };
91
+ }
92
+ };
93
+ }
94
+
95
+ // src/rules/extraction-diagnostics.ts
96
+ var extractionDiagnosticsRuleName = "extraction-diagnostics";
97
+ function createExtractionDiagnosticsRule(options) {
98
+ return {
99
+ meta: {
100
+ type: "problem",
101
+ docs: {
102
+ description: "Report Panda extraction diagnostics for the current file."
103
+ },
104
+ schema: [],
105
+ messages: {
106
+ diagnostic: "{{message}}"
107
+ }
108
+ },
109
+ create(context) {
110
+ return {
111
+ Program() {
112
+ const inspection = options.inspect(context);
113
+ if (!inspection) return;
114
+ reportDiagnostics(context, inspection.diagnostics);
115
+ }
116
+ };
117
+ }
118
+ };
119
+ }
120
+ function reportDiagnostics(context, diagnostics) {
121
+ for (const diagnostic of diagnostics) {
122
+ context.report({
123
+ messageId: "diagnostic",
124
+ data: { message: diagnostic.message },
125
+ ...diagnostic.location ? { loc: toEslintLoc(diagnostic.location) } : {}
126
+ });
127
+ }
128
+ }
129
+
130
+ // src/rules/tokens.ts
131
+ var noInvalidTokenPathsRuleName = "no-invalid-token-paths";
132
+ function createNoInvalidTokenPathsRule(options) {
133
+ return createTokenRule({
134
+ description: "Report unresolved Panda token references for the current file.",
135
+ message: 'Panda token "{{token}}" was not found.',
136
+ inspect: options.inspect,
137
+ report(inspection, context) {
138
+ for (const tokenRef of inspection.tokenRefs) {
139
+ if (tokenRef.resolved) continue;
140
+ reportToken(context, tokenRef.path, tokenRef.range);
141
+ }
142
+ }
143
+ });
144
+ }
145
+ function createTokenRule(config) {
146
+ return {
147
+ meta: {
148
+ type: "problem",
149
+ docs: {
150
+ description: config.description
151
+ },
152
+ schema: [],
153
+ messages: {
154
+ token: config.message
155
+ }
156
+ },
157
+ create(context) {
158
+ return {
159
+ Program() {
160
+ const inspection = config.inspect(context);
161
+ if (!inspection) return;
162
+ config.report(inspection, context);
163
+ }
164
+ };
165
+ }
166
+ };
167
+ }
168
+ function reportToken(context, token, range) {
169
+ context.report({
170
+ messageId: "token",
171
+ data: { token },
172
+ loc: toEslintLoc(range)
173
+ });
174
+ }
175
+
176
+ // src/core/inspector.ts
177
+ import { FileInspector, sourceCacheKey } from "@pandacss/compiler/tooling";
178
+
179
+ // src/core/project-cache.ts
180
+ import { createProjectFromConfig } from "@pandacss/compiler/tooling";
181
+ var ProjectCache = class {
182
+ #projects = /* @__PURE__ */ new Map();
183
+ #createProject;
184
+ constructor(options = {}) {
185
+ this.#createProject = options.createProject ?? ((settings) => createProjectFromConfig({ cwd: settings.cwd, configPath: settings.configPath }));
186
+ }
187
+ get(settings) {
188
+ const key = projectCacheKey(settings);
189
+ const cached = this.#projects.get(key);
190
+ if (cached) return cached;
191
+ const project = this.#createProject(settings);
192
+ this.#projects.set(key, project);
193
+ return project;
194
+ }
195
+ clear() {
196
+ this.#projects.clear();
197
+ }
198
+ };
199
+ function projectCacheKey(settings) {
200
+ return `${settings.cwd}\0${settings.configPath ?? ""}`;
201
+ }
202
+
203
+ // src/core/settings.ts
204
+ function resolvePandaSettings(context) {
205
+ const settings = context.settings ?? {};
206
+ const panda = isObject(settings.panda) ? settings.panda : {};
207
+ const migrationConfigPath = settings["@pandacss/configPath"];
208
+ return {
209
+ configPath: stringSetting(panda.configPath) ?? stringSetting(migrationConfigPath),
210
+ cwd: stringSetting(panda.cwd) ?? context.cwd ?? process.cwd(),
211
+ strictDiagnostics: panda.strictDiagnostics === true
212
+ };
213
+ }
214
+ function stringSetting(value) {
215
+ return typeof value === "string" && value.length > 0 ? value : void 0;
216
+ }
217
+ function isObject(value) {
218
+ return typeof value === "object" && value !== null;
219
+ }
220
+
221
+ // src/core/linter.ts
222
+ var Linter = class {
223
+ #projectCache;
224
+ #inspector;
225
+ constructor(options = {}) {
226
+ this.#projectCache = options.projectCache ?? new ProjectCache();
227
+ this.#inspector = options.inspector ?? new FileInspector();
228
+ }
229
+ getProject(context) {
230
+ return this.#projectCache.get(resolvePandaSettings(context));
231
+ }
232
+ async inspect(context) {
233
+ const settings = resolvePandaSettings(context);
234
+ const project = await this.#projectCache.get(settings);
235
+ const path = getContextFilename(context);
236
+ const source = getContextSource(context);
237
+ const result = this.inspectProject(project, path, source);
238
+ return { project, settings, path, source, result };
239
+ }
240
+ inspectProject(project, path, source) {
241
+ return this.#inspector.inspect(project.compiler, path, source);
242
+ }
243
+ clear() {
244
+ this.#projectCache.clear();
245
+ this.#inspector.clear();
246
+ }
247
+ };
248
+ function getContextFilename(context) {
249
+ return cleanFilename(context.physicalFilename) ?? cleanFilename(context.filename) ?? cleanFilename(context.getPhysicalFilename?.()) ?? cleanFilename(context.getFilename?.()) ?? "<unknown>";
250
+ }
251
+ function getContextSource(context) {
252
+ const sourceCode = context.sourceCode ?? context.getSourceCode?.();
253
+ return sourceCode?.text ?? sourceCode?.getText?.() ?? "";
254
+ }
255
+ function cleanFilename(value) {
256
+ if (!value || value === "<text>" || value === "<input>") return void 0;
257
+ return value;
258
+ }
259
+
260
+ // src/rules/file-not-included.ts
261
+ var fileNotIncludedRuleName = "file-not-included";
262
+ var MESSAGE = "This file uses Panda but is not part of the Panda config `include` globs, so its styles will not be generated.";
263
+ function createFileNotIncludedRule(options) {
264
+ return {
265
+ meta: {
266
+ type: "problem",
267
+ docs: {
268
+ description: "Report files that use Panda but are excluded from the config `include` globs."
269
+ },
270
+ schema: [],
271
+ messages: {
272
+ notIncluded: MESSAGE
273
+ }
274
+ },
275
+ create(context) {
276
+ return {
277
+ Program() {
278
+ const path = getContextFilename(context);
279
+ if (options.isSourceFile(path)) return;
280
+ const inspection = options.inspect(context);
281
+ if (!inspection) return;
282
+ const usesPanda = inspection.calls.length > 0 || inspection.jsx.length > 0 || inspection.styleEntries.length > 0 || inspection.tokenRefs.length > 0;
283
+ if (!usesPanda) return;
284
+ context.report({
285
+ messageId: "notIncluded",
286
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 0 } }
287
+ });
288
+ }
289
+ };
290
+ }
291
+ };
292
+ }
293
+
294
+ // src/rules/no-debug.ts
295
+ var noDebugRuleName = "no-debug";
296
+ var MESSAGE2 = "Remove the `debug` property; it logs generated styles and should not ship.";
297
+ function createNoDebugRule(options) {
298
+ return {
299
+ meta: {
300
+ type: "problem",
301
+ docs: {
302
+ description: "Report the Panda `debug` property left in source."
303
+ },
304
+ schema: [],
305
+ messages: {
306
+ debug: MESSAGE2
307
+ }
308
+ },
309
+ create(context) {
310
+ return {
311
+ Program() {
312
+ const inspection = options.inspect(context);
313
+ if (!inspection) return;
314
+ for (const entry of inspection.styleEntries) {
315
+ if (entry.name !== "debug") continue;
316
+ context.report({ messageId: "debug", loc: toEslintLoc(entry.range) });
317
+ }
318
+ }
319
+ };
320
+ }
321
+ };
322
+ }
323
+
324
+ // src/rules/no-deprecated.ts
325
+ var noDeprecatedRuleName = "no-deprecated";
326
+ var ALL_KINDS = ["tokens", "utilities", "recipes", "patterns"];
327
+ function stripModifier(path) {
328
+ const slash = path.indexOf("/");
329
+ return slash === -1 ? path : path.slice(0, slash);
330
+ }
331
+ function createNoDeprecatedRule(options) {
332
+ return {
333
+ meta: {
334
+ type: "problem",
335
+ docs: {
336
+ description: "Report usage of deprecated Panda tokens, utilities, and recipes."
337
+ },
338
+ schema: [
339
+ {
340
+ type: "object",
341
+ properties: {
342
+ kinds: { type: "array", items: { enum: [...ALL_KINDS] }, uniqueItems: true }
343
+ },
344
+ additionalProperties: false
345
+ }
346
+ ],
347
+ messages: {
348
+ deprecated: 'Panda {{kind}} "{{name}}" is deprecated.{{note}}'
349
+ }
350
+ },
351
+ create(context) {
352
+ const configured = context.options?.[0]?.kinds;
353
+ const kinds = new Set(configured ?? ALL_KINDS);
354
+ return {
355
+ Program() {
356
+ const inspection = options.inspect(context);
357
+ if (!inspection) return;
358
+ if (kinds.has("tokens")) {
359
+ for (const usage of inspection.usages) {
360
+ if (usage.kind !== "token") continue;
361
+ const path = stripModifier(usage.name);
362
+ const deprecation = options.tokens[path];
363
+ if (deprecation === void 0) continue;
364
+ report(context, "token", path, deprecation, usage.range);
365
+ }
366
+ }
367
+ if (kinds.has("utilities")) {
368
+ for (const entry of inspection.styleEntries) {
369
+ if (entry.kind !== "utility") continue;
370
+ const prop = entry.canonicalName ?? entry.name;
371
+ const deprecation = options.utilities[prop];
372
+ if (deprecation === void 0) continue;
373
+ report(context, "property", prop, deprecation, entry.range);
374
+ }
375
+ }
376
+ if (kinds.has("recipes")) {
377
+ reportRecipes(context, inspection, options.recipes);
378
+ }
379
+ if (kinds.has("patterns")) {
380
+ reportPatterns(context, inspection, options.patterns);
381
+ }
382
+ }
383
+ };
384
+ }
385
+ };
386
+ }
387
+ function reportRecipes(context, inspection, recipes) {
388
+ reportComponents(
389
+ context,
390
+ "recipe",
391
+ recipes,
392
+ inspection.componentEntries.map((entry) => ({ name: entry.recipe, range: entry.range })),
393
+ inspection.usages.filter((usage) => usage.kind === "recipe")
394
+ );
395
+ }
396
+ function reportPatterns(context, inspection, patterns) {
397
+ reportComponents(
398
+ context,
399
+ "pattern",
400
+ patterns,
401
+ inspection.componentEntries.map((entry) => ({ name: entry.pattern, range: entry.range })),
402
+ inspection.usages.filter((usage) => usage.kind === "pattern")
403
+ );
404
+ }
405
+ function reportComponents(context, kind, deprecations, fromComponents, fromUsages) {
406
+ const seen = /* @__PURE__ */ new Set();
407
+ const emit = (name, range) => {
408
+ if (!name) return;
409
+ const deprecation = deprecations[name];
410
+ if (deprecation === void 0) return;
411
+ const key = `${range.start.line}:${range.start.column}`;
412
+ if (seen.has(key)) return;
413
+ seen.add(key);
414
+ report(context, kind, name, deprecation, range);
415
+ };
416
+ for (const entry of fromComponents) emit(entry.name, entry.range);
417
+ for (const usage of fromUsages) emit(usage.name, usage.range);
418
+ }
419
+ function report(context, kind, name, deprecation, range) {
420
+ const note = typeof deprecation === "string" ? ` ${deprecation}` : "";
421
+ context.report({
422
+ messageId: "deprecated",
423
+ data: { kind, name, note },
424
+ loc: toEslintLoc(range)
425
+ });
426
+ }
427
+
428
+ // src/rules/no-important.ts
429
+ var noImportantRuleName = "no-important";
430
+ var MESSAGE3 = "Avoid `!important`; it escalates specificity and is hard to override.";
431
+ function hasImportant(value) {
432
+ if (typeof value === "string") {
433
+ const v = value.trim().toLowerCase();
434
+ return v.endsWith("!") || v.endsWith("!important");
435
+ }
436
+ if (Array.isArray(value)) return value.some(hasImportant);
437
+ if (value && typeof value === "object") return Object.values(value).some(hasImportant);
438
+ return false;
439
+ }
440
+ function createNoImportantRule(options) {
441
+ return {
442
+ meta: {
443
+ type: "problem",
444
+ docs: { description: "Disallow `!important` in Panda styles." },
445
+ schema: [],
446
+ messages: { important: MESSAGE3 }
447
+ },
448
+ create(context) {
449
+ return {
450
+ Program() {
451
+ const inspection = options.inspect(context);
452
+ if (!inspection) return;
453
+ for (const entry of inspection.styleEntries) {
454
+ if (entry.kind === "condition" || entry.kind === "selector") continue;
455
+ if (hasImportant(entry.resolvedValue)) {
456
+ context.report({ messageId: "important", loc: toEslintLoc(entry.range) });
457
+ }
458
+ }
459
+ }
460
+ };
461
+ }
462
+ };
463
+ }
464
+
465
+ // src/rules/no-invalid-nesting.ts
466
+ var noInvalidNestingRuleName = "no-invalid-nesting";
467
+ function isSelectorKey(name) {
468
+ const trimmed = name.trim();
469
+ return /^[:.#[*>+~]/.test(trimmed) || trimmed.includes(":");
470
+ }
471
+ function isObject2(value) {
472
+ return typeof value === "object" && value !== null && !Array.isArray(value);
473
+ }
474
+ function createNoInvalidNestingRule(options) {
475
+ return {
476
+ meta: {
477
+ type: "problem",
478
+ docs: { description: 'Disallow nested selectors that omit "&", which Panda silently ignores.' },
479
+ schema: [],
480
+ messages: {
481
+ nesting: 'Nested selector "{{selector}}" has no "&", so Panda ignores it. Use "{{fixed}}" or a condition like "_hover".'
482
+ },
483
+ hasSuggestions: true
484
+ },
485
+ create(context) {
486
+ const source = getSourceText(context);
487
+ return {
488
+ Program() {
489
+ const inspection = options.inspect(context);
490
+ if (!inspection) return;
491
+ for (const entry of inspection.styleEntries) {
492
+ const nestable = entry.kind === "unknown" || entry.kind === "recipe-variant";
493
+ if (!nestable || !isSelectorKey(entry.name) || !isObject2(entry.resolvedValue)) continue;
494
+ const fixed = `&${entry.name}`;
495
+ const descriptor = {
496
+ messageId: "nesting",
497
+ data: { selector: entry.name, fixed },
498
+ loc: toEslintLoc(entry.range)
499
+ };
500
+ if (entry.fixable === "safe" && entry.keySpan && source) {
501
+ const range = [
502
+ byteToIndex(source, entry.keySpan.start),
503
+ byteToIndex(source, entry.keySpan.end)
504
+ ];
505
+ descriptor.suggest = [
506
+ { desc: `Prefix with "&" \u2192 "${fixed}"`, fix: (fixer) => fixer.replaceTextRange(range, `'${fixed}'`) }
507
+ ];
508
+ }
509
+ context.report(descriptor);
510
+ }
511
+ }
512
+ };
513
+ }
514
+ };
515
+ }
516
+
517
+ // src/rules/no-primitive-token.ts
518
+ var noPrimitiveTokenRuleName = "no-primitive-token";
519
+ var MAX_SUGGESTIONS = 3;
520
+ function isAllowed(token, allow) {
521
+ if (allow.size === 0) return false;
522
+ return allow.has(token.path) || allow.has(token.categoryPath);
523
+ }
524
+ function modifierSuffix(token, sourcePath) {
525
+ if (sourcePath?.startsWith(`${token.path}/`)) return sourcePath.slice(token.path.length);
526
+ return token.modifier ? `/${token.modifier}` : "";
527
+ }
528
+ function shouldReport(token, config) {
529
+ if (token.semantic || !token.semanticCategory) return false;
530
+ if (config.categories && !config.categories.has(token.category)) return false;
531
+ return !isAllowed(token, config.allow);
532
+ }
533
+ function replacementForRange(source, range, value) {
534
+ const current = source.slice(range[0], range[1]);
535
+ const quote = current[0];
536
+ return quote === '"' || quote === "'" || quote === "`" ? `${quote}${value}${quote}` : value;
537
+ }
538
+ function tokenRefFixRange(source, span, token) {
539
+ const outerStart = byteToIndex(source, span.start);
540
+ const outerEnd = byteToIndex(source, span.end);
541
+ const text = source.slice(outerStart, outerEnd);
542
+ for (const quote of ["'", '"', "`"]) {
543
+ const needle = `${quote}${token}${quote}`;
544
+ const index2 = text.indexOf(needle);
545
+ if (index2 >= 0) return [outerStart + index2, outerStart + index2 + needle.length];
546
+ }
547
+ const index = text.indexOf(token);
548
+ return index >= 0 ? [outerStart + index, outerStart + index + token.length] : void 0;
549
+ }
550
+ function tokenRefSourcePath(source, span, path) {
551
+ const outerStart = byteToIndex(source, span.start);
552
+ const outerEnd = byteToIndex(source, span.end);
553
+ const text = source.slice(outerStart, outerEnd);
554
+ const index = text.indexOf(path);
555
+ if (index < 0) return path;
556
+ let end = index + path.length;
557
+ if (text[end] === "/") {
558
+ end += 1;
559
+ while (end < text.length && !["'", '"', "`", ",", ")", " ", "\n", "\r", " "].includes(text[end])) end += 1;
560
+ }
561
+ return text.slice(index, end);
562
+ }
563
+ function reportPrimitiveToken(context, source, config, token, loc, fixRange, replacementPath, sourcePath) {
564
+ if (!shouldReport(token, config)) return;
565
+ const suffix = modifierSuffix(token, sourcePath);
566
+ const suggestions = config.suggestSemanticTokens(token.path).slice(0, MAX_SUGGESTIONS);
567
+ const descriptor = {
568
+ messageId: "primitive",
569
+ data: { category: token.category, token: token.categoryPath },
570
+ loc: toEslintLoc(loc)
571
+ };
572
+ if (source && fixRange && suggestions.length > 0) {
573
+ descriptor.suggest = suggestions.map((suggestion) => {
574
+ const replacement = replacementPath(suggestion, suffix);
575
+ return {
576
+ desc: `Use the semantic token "${replacement}"`,
577
+ fix: (fixer) => fixer.replaceTextRange(fixRange, replacementForRange(source, fixRange, replacement))
578
+ };
579
+ });
580
+ }
581
+ context.report(descriptor);
582
+ }
583
+ function reportStyleEntries(context, inspection, config, source) {
584
+ for (const entry of inspection.styleEntries) {
585
+ if (entry.kind !== "utility") continue;
586
+ for (const leaf of entry.valueSpans ?? []) {
587
+ if (!leaf.token) continue;
588
+ const fixRange = source ? [byteToIndex(source, leaf.span.start), byteToIndex(source, leaf.span.end)] : void 0;
589
+ reportPrimitiveToken(context, source, config, leaf.token, entry.range, fixRange, (suggestion, suffix) => {
590
+ return `${suggestion.token}${suffix}`;
591
+ });
592
+ }
593
+ }
594
+ }
595
+ function reportTokenRefs(context, inspection, config, source) {
596
+ for (const tokenRef of inspection.tokenRefs) {
597
+ if (!tokenRef.resolved || !tokenRef.token) continue;
598
+ const sourcePath = source ? tokenRefSourcePath(source, tokenRef.span, tokenRef.path) : tokenRef.path;
599
+ const fixRange = source ? tokenRefFixRange(source, tokenRef.span, sourcePath) : void 0;
600
+ reportPrimitiveToken(
601
+ context,
602
+ source,
603
+ config,
604
+ tokenRef.token,
605
+ tokenRef.range,
606
+ fixRange,
607
+ (suggestion, suffix) => `${tokenRef.token?.category}.${suggestion.token}${suffix}`,
608
+ sourcePath
609
+ );
610
+ }
611
+ }
612
+ function reportPrimitiveTokenViolations(context, inspection, config) {
613
+ const source = getSourceText(context);
614
+ reportStyleEntries(context, inspection, config, source);
615
+ reportTokenRefs(context, inspection, config, source);
616
+ }
617
+ function createNoPrimitiveTokenRule(options) {
618
+ return {
619
+ meta: {
620
+ type: "suggestion",
621
+ docs: { description: "Prefer semantic tokens over primitive tokens when semantic tokens exist for a category." },
622
+ schema: [
623
+ {
624
+ type: "object",
625
+ properties: {
626
+ categories: { type: "array", items: { type: "string" }, uniqueItems: true },
627
+ allow: { type: "array", items: { type: "string" }, uniqueItems: true }
628
+ },
629
+ additionalProperties: false
630
+ }
631
+ ],
632
+ messages: {
633
+ primitive: 'Use a semantic {{category}} token instead of the primitive token "{{token}}".'
634
+ },
635
+ hasSuggestions: true
636
+ },
637
+ create(context) {
638
+ const configured = context.options?.[0];
639
+ const categories = configured?.categories ? new Set(configured.categories) : void 0;
640
+ const allow = new Set(configured?.allow ?? []);
641
+ return {
642
+ Program() {
643
+ const inspection = options.inspect(context);
644
+ if (!inspection) return;
645
+ reportPrimitiveTokenViolations(context, inspection, { ...options, categories, allow });
646
+ }
647
+ };
648
+ }
649
+ };
650
+ }
651
+
652
+ // src/rules/no-margin-properties.ts
653
+ var noMarginPropertiesRuleName = "no-margin-properties";
654
+ var MESSAGE4 = "Avoid margin properties; prefer `gap` or a layout pattern for spacing.";
655
+ function createNoMarginPropertiesRule(options) {
656
+ return {
657
+ meta: {
658
+ type: "suggestion",
659
+ docs: { description: "Disallow margin properties in favor of gap/layout patterns." },
660
+ schema: [],
661
+ messages: { margin: MESSAGE4 }
662
+ },
663
+ create(context) {
664
+ return {
665
+ Program() {
666
+ const inspection = options.inspect(context);
667
+ if (!inspection) return;
668
+ for (const entry of inspection.styleEntries) {
669
+ if (entry.kind !== "utility") continue;
670
+ const prop = entry.canonicalName ?? entry.name;
671
+ if (prop.startsWith("margin")) {
672
+ context.report({ messageId: "margin", loc: toEslintLoc(entry.range) });
673
+ }
674
+ }
675
+ }
676
+ };
677
+ }
678
+ };
679
+ }
680
+
681
+ // src/rules/no-physical-properties.ts
682
+ var noPhysicalPropertiesRuleName = "no-physical-properties";
683
+ var PHYSICAL = {
684
+ left: "insetInlineStart",
685
+ right: "insetInlineEnd",
686
+ top: "insetBlockStart",
687
+ bottom: "insetBlockEnd",
688
+ marginLeft: "marginInlineStart",
689
+ marginRight: "marginInlineEnd",
690
+ marginTop: "marginBlockStart",
691
+ marginBottom: "marginBlockEnd",
692
+ paddingLeft: "paddingInlineStart",
693
+ paddingRight: "paddingInlineEnd",
694
+ paddingTop: "paddingBlockStart",
695
+ paddingBottom: "paddingBlockEnd",
696
+ borderLeft: "borderInlineStart",
697
+ borderRight: "borderInlineEnd",
698
+ borderTop: "borderBlockStart",
699
+ borderBottom: "borderBlockEnd",
700
+ borderLeftWidth: "borderInlineStartWidth",
701
+ borderRightWidth: "borderInlineEndWidth",
702
+ borderTopWidth: "borderBlockStartWidth",
703
+ borderBottomWidth: "borderBlockEndWidth",
704
+ borderTopLeftRadius: "borderStartStartRadius",
705
+ borderTopRightRadius: "borderStartEndRadius",
706
+ borderBottomLeftRadius: "borderEndStartRadius",
707
+ borderBottomRightRadius: "borderEndEndRadius"
708
+ };
709
+ function createNoPhysicalPropertiesRule(options) {
710
+ return {
711
+ meta: {
712
+ type: "suggestion",
713
+ docs: { description: "Disallow physical properties that have logical equivalents." },
714
+ schema: [],
715
+ messages: { physical: 'Use the logical property "{{logical}}" instead of the physical "{{physical}}".' }
716
+ },
717
+ create(context) {
718
+ return {
719
+ Program() {
720
+ const inspection = options.inspect(context);
721
+ if (!inspection) return;
722
+ for (const entry of inspection.styleEntries) {
723
+ if (entry.kind !== "utility") continue;
724
+ const prop = entry.canonicalName ?? entry.name;
725
+ const logical = PHYSICAL[prop];
726
+ if (logical === void 0) continue;
727
+ context.report({
728
+ messageId: "physical",
729
+ data: { logical, physical: prop },
730
+ loc: toEslintLoc(entry.range)
731
+ });
732
+ }
733
+ }
734
+ };
735
+ }
736
+ };
737
+ }
738
+
739
+ // src/rules/no-shorthand-longhand-mix.ts
740
+ var noShorthandLonghandMixRuleName = "no-shorthand-longhand-mix";
741
+ var COMPOSITE_PROPERTIES = {
742
+ animation: [
743
+ "animationName",
744
+ "animationDuration",
745
+ "animationTimingFunction",
746
+ "animationDelay",
747
+ "animationIterationCount",
748
+ "animationDirection",
749
+ "animationFillMode",
750
+ "animationPlayState"
751
+ ],
752
+ background: [
753
+ "backgroundImage",
754
+ "backgroundPosition",
755
+ "backgroundSize",
756
+ "backgroundRepeat",
757
+ "backgroundAttachment",
758
+ "backgroundOrigin",
759
+ "backgroundClip",
760
+ "backgroundColor"
761
+ ],
762
+ backgroundPosition: ["backgroundPositionX", "backgroundPositionY"],
763
+ border: ["borderWidth", "borderStyle", "borderColor"],
764
+ borderBlock: ["borderBlockWidth", "borderBlockStyle", "borderBlockColor"],
765
+ borderBlockColor: ["borderBlockStartColor", "borderBlockEndColor"],
766
+ borderBlockEnd: ["borderBlockEndWidth", "borderBlockEndStyle", "borderBlockEndColor"],
767
+ borderBlockStart: ["borderBlockStartWidth", "borderBlockStartStyle", "borderBlockStartColor"],
768
+ borderBlockStyle: ["borderBlockStartStyle", "borderBlockEndStyle"],
769
+ borderBlockWidth: ["borderBlockStartWidth", "borderBlockEndWidth"],
770
+ borderBottom: ["borderBottomWidth", "borderBottomStyle", "borderBottomColor"],
771
+ borderColor: ["borderTopColor", "borderRightColor", "borderBottomColor", "borderLeftColor"],
772
+ borderImage: ["borderImageSource", "borderImageSlice", "borderImageWidth", "borderImageOutset", "borderImageRepeat"],
773
+ borderInline: ["borderInlineWidth", "borderInlineStyle", "borderInlineColor"],
774
+ borderInlineColor: ["borderInlineStartColor", "borderInlineEndColor"],
775
+ borderInlineEnd: ["borderInlineEndWidth", "borderInlineEndStyle", "borderInlineEndColor"],
776
+ borderInlineStart: ["borderInlineStartWidth", "borderInlineStartStyle", "borderInlineStartColor"],
777
+ borderInlineStyle: ["borderInlineStartStyle", "borderInlineEndStyle"],
778
+ borderInlineWidth: ["borderInlineStartWidth", "borderInlineEndWidth"],
779
+ borderLeft: ["borderLeftWidth", "borderLeftStyle", "borderLeftColor"],
780
+ borderRadius: ["borderTopLeftRadius", "borderTopRightRadius", "borderBottomRightRadius", "borderBottomLeftRadius"],
781
+ borderRight: ["borderRightWidth", "borderRightStyle", "borderRightColor"],
782
+ borderStyle: ["borderTopStyle", "borderRightStyle", "borderBottomStyle", "borderLeftStyle"],
783
+ borderTop: ["borderTopWidth", "borderTopStyle", "borderTopColor"],
784
+ borderWidth: ["borderTopWidth", "borderRightWidth", "borderBottomWidth", "borderLeftWidth"],
785
+ columnRule: ["columnRuleWidth", "columnRuleStyle", "columnRuleColor"],
786
+ columns: ["columnWidth", "columnCount"],
787
+ containIntrinsicSize: ["containIntrinsicSizeInline", "containIntrinsicSizeBlock"],
788
+ flex: ["flexGrow", "flexShrink", "flexBasis"],
789
+ flexFlow: ["flexDirection", "flexWrap"],
790
+ font: [
791
+ "fontStyle",
792
+ "fontVariantCaps",
793
+ "fontVariantEastAsian",
794
+ "fontVariantLigatures",
795
+ "fontVariantNumeric",
796
+ "fontVariantPosition",
797
+ "fontWeight",
798
+ "fontStretch",
799
+ "fontSize",
800
+ "lineHeight",
801
+ "fontFamily"
802
+ ],
803
+ fontSynthesis: ["fontSynthesisWeight", "fontSynthesisStyle", "fontSynthesisSmallCaps"],
804
+ fontVariant: [
805
+ "fontVariantCaps",
806
+ "fontVariantEastAsian",
807
+ "fontVariantLigatures",
808
+ "fontVariantNumeric",
809
+ "fontVariantPosition"
810
+ ],
811
+ gap: ["columnGap", "rowGap"],
812
+ grid: [
813
+ "gridTemplateColumns",
814
+ "gridTemplateRows",
815
+ "gridTemplateAreas",
816
+ "gridAutoColumns",
817
+ "gridAutoRows",
818
+ "gridAutoFlow"
819
+ ],
820
+ gridArea: ["gridRowStart", "gridColumnStart", "gridRowEnd", "gridColumnEnd"],
821
+ gridColumn: ["gridColumnStart", "gridColumnEnd"],
822
+ gridGap: ["gridColumnGap", "gridRowGap"],
823
+ gridRow: ["gridRowStart", "gridRowEnd"],
824
+ gridTemplate: ["gridTemplateColumns", "gridTemplateRows", "gridTemplateAreas"],
825
+ inset: ["top", "right", "bottom", "left"],
826
+ insetBlock: ["insetBlockStart", "insetBlockEnd"],
827
+ insetInline: ["insetInlineStart", "insetInlineEnd"],
828
+ listStyle: ["listStyleType", "listStylePosition", "listStyleImage"],
829
+ margin: ["marginTop", "marginRight", "marginBottom", "marginLeft"],
830
+ marginBlock: ["marginBlockStart", "marginBlockEnd"],
831
+ marginInline: ["marginInlineStart", "marginInlineEnd"],
832
+ mask: ["maskImage", "maskMode", "maskRepeat", "maskPosition", "maskClip", "maskOrigin", "maskSize", "maskComposite"],
833
+ maskBorder: [
834
+ "maskBorderSource",
835
+ "maskBorderMode",
836
+ "maskBorderSlice",
837
+ "maskBorderWidth",
838
+ "maskBorderOutset",
839
+ "maskBorderRepeat"
840
+ ],
841
+ offset: ["offsetPosition", "offsetPath", "offsetDistance", "offsetRotate", "offsetAnchor"],
842
+ outline: ["outlineWidth", "outlineStyle", "outlineColor"],
843
+ overflow: ["overflowX", "overflowY"],
844
+ padding: ["paddingTop", "paddingRight", "paddingBottom", "paddingLeft"],
845
+ paddingBlock: ["paddingBlockStart", "paddingBlockEnd"],
846
+ paddingInline: ["paddingInlineStart", "paddingInlineEnd"],
847
+ placeContent: ["alignContent", "justifyContent"],
848
+ placeItems: ["alignItems", "justifyItems"],
849
+ placeSelf: ["alignSelf", "justifySelf"],
850
+ scrollMargin: ["scrollMarginTop", "scrollMarginRight", "scrollMarginBottom", "scrollMarginLeft"],
851
+ scrollMarginBlock: ["scrollMarginBlockStart", "scrollMarginBlockEnd"],
852
+ scrollMarginInline: ["scrollMarginInlineStart", "scrollMarginInlineEnd"],
853
+ scrollPadding: ["scrollPaddingTop", "scrollPaddingRight", "scrollPaddingBottom", "scrollPaddingLeft"],
854
+ scrollPaddingBlock: ["scrollPaddingBlockStart", "scrollPaddingBlockEnd"],
855
+ scrollPaddingInline: ["scrollPaddingInlineStart", "scrollPaddingInlineEnd"],
856
+ textDecoration: ["textDecorationLine", "textDecorationStyle", "textDecorationColor"],
857
+ textEmphasis: ["textEmphasisStyle", "textEmphasisColor"],
858
+ transition: ["transitionProperty", "transitionDuration", "transitionTimingFunction", "transitionDelay"]
859
+ };
860
+ function createNoShorthandLonghandMixRule(options) {
861
+ return {
862
+ meta: {
863
+ type: "suggestion",
864
+ docs: { description: "Disallow mixing a shorthand property with its longhand pieces in the same style block." },
865
+ schema: [
866
+ {
867
+ type: "object",
868
+ properties: { ignore: { type: "array", items: { type: "string" }, uniqueItems: true } },
869
+ additionalProperties: false
870
+ }
871
+ ],
872
+ messages: {
873
+ mix: '"{{shorthand}}" is mixed with {{longhands}}. Panda emits longhands after shorthands, so {{longhands}} will win regardless of source order; use the longhand properties for a predictable result.'
874
+ }
875
+ },
876
+ create(context) {
877
+ const configured = context.options?.[0];
878
+ const ignore = new Set(configured?.ignore ?? []);
879
+ return {
880
+ Program() {
881
+ const inspection = options.inspect(context);
882
+ if (!inspection) return;
883
+ const groups = /* @__PURE__ */ new Map();
884
+ for (const entry of inspection.styleEntries) {
885
+ if (entry.kind !== "utility") continue;
886
+ const parent = entry.path.slice(0, -1).join(".");
887
+ const key = [entry.owner.kind, entry.owner.index, parent].join("|");
888
+ const group = groups.get(key) ?? /* @__PURE__ */ new Map();
889
+ group.set(entry.canonicalName ?? entry.name, entry);
890
+ groups.set(key, group);
891
+ }
892
+ for (const group of groups.values()) {
893
+ for (const [prop, entry] of group) {
894
+ if (ignore.has(prop)) continue;
895
+ const pieces = COMPOSITE_PROPERTIES[prop];
896
+ if (!pieces) continue;
897
+ const mixed = pieces.filter((piece) => group.has(piece));
898
+ if (mixed.length === 0) continue;
899
+ const list = mixed.map((piece) => `"${piece}"`).join(", ");
900
+ context.report({
901
+ messageId: "mix",
902
+ data: { shorthand: prop, longhands: list },
903
+ loc: toEslintLoc(entry.range)
904
+ });
905
+ }
906
+ }
907
+ }
908
+ };
909
+ }
910
+ };
911
+ }
912
+
913
+ // src/rules/prefer-token.ts
914
+ var preferTokenRuleName = "prefer-token";
915
+ var MAX_LISTED = 3;
916
+ function collectStrings(value, out) {
917
+ if (typeof value === "string") out.push(value);
918
+ else if (Array.isArray(value)) for (const item of value) collectStrings(item, out);
919
+ else if (value && typeof value === "object") for (const item of Object.values(value)) collectStrings(item, out);
920
+ }
921
+ var label = (s) => s.conditional ? `${s.token} (themed)` : s.token;
922
+ function reportTokenViolations(context, inspection, config) {
923
+ const source = getSourceText(context);
924
+ for (const entry of inspection.styleEntries) {
925
+ if (entry.kind !== "utility") continue;
926
+ const category = config.categoryOf(entry.canonicalName ?? entry.name);
927
+ if (!category) continue;
928
+ if (config.categories && !config.categories.has(category)) continue;
929
+ const values = [];
930
+ collectStrings(entry.resolvedValue, values);
931
+ const hit = values.find((value) => !config.allow.has(value.trim()) && config.isHardcodedValue(entry.name, value));
932
+ if (hit === void 0) continue;
933
+ const suggestions = config.suggest(entry.name, hit).slice(0, MAX_LISTED);
934
+ const descriptor = suggestions.length ? {
935
+ messageId: "listed",
936
+ data: { category, value: hit, tokens: suggestions.map(label).join(", ") },
937
+ loc: toEslintLoc(entry.range)
938
+ } : { messageId: "generic", data: { category, value: hit }, loc: toEslintLoc(entry.range) };
939
+ const leaf = entry.valueSpans?.find((v) => v.value === hit);
940
+ if (suggestions.length > 0 && leaf && source) {
941
+ const range = [byteToIndex(source, leaf.span.start), byteToIndex(source, leaf.span.end)];
942
+ descriptor.suggest = suggestions.map((s) => ({
943
+ desc: `Use the token "${s.token}"`,
944
+ fix: (fixer) => fixer.replaceTextRange(range, `'${s.token}'`)
945
+ }));
946
+ }
947
+ context.report(descriptor);
948
+ }
949
+ }
950
+ function createPreferTokenRule(options) {
951
+ return {
952
+ meta: {
953
+ type: "suggestion",
954
+ docs: { description: "Prefer design tokens over hardcoded values on token-backed properties." },
955
+ schema: [
956
+ {
957
+ type: "object",
958
+ properties: {
959
+ categories: { type: "array", items: { type: "string" }, uniqueItems: true },
960
+ allow: { type: "array", items: { type: "string" }, uniqueItems: true }
961
+ },
962
+ additionalProperties: false
963
+ }
964
+ ],
965
+ messages: {
966
+ listed: 'Hardcoded {{category}} value "{{value}}". Matching tokens: {{tokens}}.',
967
+ generic: 'Use a {{category}} token instead of the hardcoded value "{{value}}".'
968
+ },
969
+ hasSuggestions: true
970
+ },
971
+ create(context) {
972
+ const configured = context.options?.[0];
973
+ const categories = configured?.categories ? new Set(configured.categories) : void 0;
974
+ const allow = new Set(configured?.allow ?? []);
975
+ return {
976
+ Program() {
977
+ const inspection = options.inspect(context);
978
+ if (!inspection) return;
979
+ reportTokenViolations(context, inspection, {
980
+ categoryOf: options.categoryOf,
981
+ isHardcodedValue: options.isHardcodedValue,
982
+ suggest: options.suggest,
983
+ categories,
984
+ allow
985
+ });
986
+ }
987
+ };
988
+ }
989
+ };
990
+ }
991
+
992
+ // src/rules/prefer-text-style.ts
993
+ var preferTextStyleRuleName = "prefer-text-style";
994
+ var MESSAGE5 = "Multiple typography properties set together; prefer a `textStyle` token.";
995
+ var TYPOGRAPHY = /* @__PURE__ */ new Set([
996
+ "fontSize",
997
+ "fontWeight",
998
+ "lineHeight",
999
+ "letterSpacing",
1000
+ "fontFamily",
1001
+ "textDecoration",
1002
+ "textTransform",
1003
+ "fontStyle"
1004
+ ]);
1005
+ function clustersTextStyle(value) {
1006
+ if (Array.isArray(value)) return value.some(clustersTextStyle);
1007
+ if (value && typeof value === "object") {
1008
+ const obj = value;
1009
+ const count = Object.keys(obj).filter((key) => TYPOGRAPHY.has(key)).length;
1010
+ if (count >= 2) return true;
1011
+ return Object.values(obj).some(clustersTextStyle);
1012
+ }
1013
+ return false;
1014
+ }
1015
+ function createPreferTextStyleRule(options) {
1016
+ return {
1017
+ meta: {
1018
+ type: "suggestion",
1019
+ docs: { description: "Prefer a `textStyle` token over multiple typography properties." },
1020
+ schema: [],
1021
+ messages: { textStyle: MESSAGE5 }
1022
+ },
1023
+ create(context) {
1024
+ return {
1025
+ Program() {
1026
+ const inspection = options.inspect(context);
1027
+ if (!inspection) return;
1028
+ const report2 = (range) => context.report({ messageId: "textStyle", loc: toEslintLoc(range) });
1029
+ for (const call of inspection.calls) {
1030
+ if (call.data.some((arg) => clustersTextStyle(arg.value))) report2(call.range);
1031
+ }
1032
+ for (const jsx of inspection.jsx) {
1033
+ if (clustersTextStyle(jsx.data)) report2(jsx.range);
1034
+ }
1035
+ }
1036
+ };
1037
+ }
1038
+ };
1039
+ }
1040
+
1041
+ // src/plugin.ts
1042
+ import { SpecIndex } from "@pandacss/compiler/tooling";
1043
+
1044
+ // package.json
1045
+ var package_default = {
1046
+ name: "@pandacss/eslint-plugin",
1047
+ version: "2.0.0-beta.11",
1048
+ description: "ESLint rules for Panda CSS",
1049
+ keywords: [
1050
+ "eslint",
1051
+ "eslintplugin",
1052
+ "eslint-plugin",
1053
+ "panda",
1054
+ "pandacss",
1055
+ "css-in-js"
1056
+ ],
1057
+ type: "module",
1058
+ main: "./dist/index.js",
1059
+ types: "./dist/index.d.ts",
1060
+ exports: {
1061
+ ".": {
1062
+ source: "./src/index.ts",
1063
+ types: "./dist/index.d.ts",
1064
+ default: "./dist/index.js"
1065
+ },
1066
+ "./oxlint": {
1067
+ source: "./src/oxlint.ts",
1068
+ types: "./dist/oxlint.d.ts",
1069
+ default: "./dist/oxlint.js"
1070
+ },
1071
+ "./package.json": "./package.json"
1072
+ },
1073
+ author: "Segun Adebayo <joseshegs@gmail.com>",
1074
+ license: "MIT",
1075
+ sideEffects: false,
1076
+ homepage: "https://panda-css.com",
1077
+ repository: {
1078
+ type: "git",
1079
+ url: "git+https://github.com/chakra-ui/panda.git",
1080
+ directory: "packages/eslint-plugin"
1081
+ },
1082
+ publishConfig: {
1083
+ access: "public"
1084
+ },
1085
+ scripts: {
1086
+ build: "tsup src/index.ts src/oxlint.ts --format=esm --platform=node --dts",
1087
+ "build:fast": "tsup src/index.ts src/oxlint.ts --format=esm --platform=node --no-dts",
1088
+ dev: "pnpm build:fast --watch",
1089
+ test: "vitest run"
1090
+ },
1091
+ files: [
1092
+ "dist"
1093
+ ],
1094
+ dependencies: {
1095
+ "@pandacss/compiler": "workspace:*",
1096
+ "@pandacss/compiler-shared": "workspace:*",
1097
+ "@pandacss/config": "workspace:*"
1098
+ },
1099
+ devDependencies: {
1100
+ "@typescript-eslint/parser": "8.58.2",
1101
+ eslint: "9.39.1"
1102
+ },
1103
+ peerDependencies: {
1104
+ eslint: ">=9"
1105
+ },
1106
+ engines: {
1107
+ node: ">=22"
1108
+ }
1109
+ };
1110
+
1111
+ // src/plugin.ts
1112
+ var pluginMeta = { name: "@pandacss/eslint-plugin", version: package_default.version };
1113
+ var DOCS_URL = "https://github.com/chakra-ui/panda/blob/main/packages/eslint-plugin/README.md#rules";
1114
+ async function loadPandaProject(options = {}) {
1115
+ const linter = options.linter ?? new Linter();
1116
+ const project = await linter.getProject({
1117
+ cwd: options.cwd ?? process.cwd(),
1118
+ settings: options.configPath ? { panda: { configPath: options.configPath } } : {}
1119
+ });
1120
+ return { linter, project };
1121
+ }
1122
+ var RAW_VALUE_KEYWORDS = /* @__PURE__ */ new Set([
1123
+ "inherit",
1124
+ "initial",
1125
+ "unset",
1126
+ "revert",
1127
+ "revert-layer",
1128
+ "none",
1129
+ "auto",
1130
+ "currentcolor",
1131
+ "transparent"
1132
+ ]);
1133
+ function hardcodedValueClassifier(compiler) {
1134
+ const cache = /* @__PURE__ */ new Map();
1135
+ return (prop, value) => {
1136
+ const key = `${prop}\0${value}`;
1137
+ const cached = cache.get(key);
1138
+ if (cached !== void 0) return cached;
1139
+ let result = false;
1140
+ if (!RAW_VALUE_KEYWORDS.has(value.trim().toLowerCase())) {
1141
+ const resolved = compiler.resolveUtilityValue({ prop, value });
1142
+ result = resolved !== null && !String(resolved.cssValue).trimStart().startsWith("var(");
1143
+ }
1144
+ cache.set(key, result);
1145
+ return result;
1146
+ };
1147
+ }
1148
+ function recipeDeprecations(spec) {
1149
+ const out = {};
1150
+ for (const recipe of Object.values(spec.recipes)) {
1151
+ if (recipe.deprecated !== void 0) out[recipe.name] = recipe.deprecated;
1152
+ }
1153
+ for (const recipe of Object.values(spec.slotRecipes)) {
1154
+ if (recipe.deprecated !== void 0) out[recipe.name] = recipe.deprecated;
1155
+ }
1156
+ return out;
1157
+ }
1158
+ function patternDeprecations(spec) {
1159
+ const out = {};
1160
+ for (const pattern of Object.values(spec.patterns)) {
1161
+ if (pattern.deprecated !== void 0) out[pattern.name] = pattern.deprecated;
1162
+ }
1163
+ return out;
1164
+ }
1165
+ function bindRules(linter, project) {
1166
+ const spec = project.compiler.spec();
1167
+ const specIndex = new SpecIndex(spec);
1168
+ const categoryOf = (prop) => specIndex.resolveTokenCategoryForProperty(prop);
1169
+ const isHardcodedValue = hardcodedValueClassifier(project.compiler);
1170
+ const suggest = (prop, value) => project.compiler.suggestTokens(prop, value);
1171
+ const suggestSemanticTokens = (path) => project.compiler.suggestSemanticTokens(path);
1172
+ const inspect = (context) => linter.inspectProject(project, getContextFilename(context), getContextSource(context));
1173
+ const rules = {
1174
+ [extractionDiagnosticsRuleName]: createExtractionDiagnosticsRule({ inspect }),
1175
+ [fileNotIncludedRuleName]: createFileNotIncludedRule({
1176
+ inspect,
1177
+ isSourceFile: (path) => project.compiler.isSourceFile(path)
1178
+ }),
1179
+ [noInvalidTokenPathsRuleName]: createNoInvalidTokenPathsRule({ inspect }),
1180
+ [noInvalidNestingRuleName]: createNoInvalidNestingRule({ inspect }),
1181
+ [noDeprecatedRuleName]: createNoDeprecatedRule({
1182
+ inspect,
1183
+ tokens: spec.tokens.deprecated,
1184
+ utilities: spec.utilities.deprecated,
1185
+ recipes: recipeDeprecations(spec),
1186
+ patterns: patternDeprecations(spec)
1187
+ }),
1188
+ [noDebugRuleName]: createNoDebugRule({ inspect }),
1189
+ // `recommended` scopes prefer-token to colors (the old `no-hardcoded-color`).
1190
+ [preferTokenRuleName]: createPreferTokenRule({ inspect, categoryOf, isHardcodedValue, suggest }),
1191
+ [noPrimitiveTokenRuleName]: createNoPrimitiveTokenRule({ inspect, suggestSemanticTokens }),
1192
+ // Opt-in style/enforcement rules (not in `recommended`).
1193
+ [noImportantRuleName]: createNoImportantRule({ inspect }),
1194
+ [noMarginPropertiesRuleName]: createNoMarginPropertiesRule({ inspect }),
1195
+ [noPhysicalPropertiesRuleName]: createNoPhysicalPropertiesRule({ inspect }),
1196
+ [noShorthandLonghandMixRuleName]: createNoShorthandLonghandMixRule({ inspect }),
1197
+ [consistentPropertyStyleRuleName]: createConsistentPropertyStyleRule({
1198
+ inspect,
1199
+ shorthands: spec.utilities.shorthands
1200
+ }),
1201
+ [preferTextStyleRuleName]: createPreferTextStyleRule({ inspect })
1202
+ };
1203
+ for (const rule of Object.values(rules)) {
1204
+ rule.meta.docs.url = DOCS_URL;
1205
+ }
1206
+ return rules;
1207
+ }
1208
+ async function createPandaPlugin(options = {}) {
1209
+ const { linter, project } = await loadPandaProject(options);
1210
+ return { rules: bindRules(linter, project) };
1211
+ }
1212
+
1213
+ export {
1214
+ getSourceText,
1215
+ byteToIndex,
1216
+ toEslintLoc,
1217
+ consistentPropertyStyleRuleName,
1218
+ createConsistentPropertyStyleRule,
1219
+ extractionDiagnosticsRuleName,
1220
+ createExtractionDiagnosticsRule,
1221
+ reportDiagnostics,
1222
+ noInvalidTokenPathsRuleName,
1223
+ createNoInvalidTokenPathsRule,
1224
+ FileInspector,
1225
+ sourceCacheKey,
1226
+ ProjectCache,
1227
+ projectCacheKey,
1228
+ resolvePandaSettings,
1229
+ Linter,
1230
+ getContextFilename,
1231
+ getContextSource,
1232
+ fileNotIncludedRuleName,
1233
+ createFileNotIncludedRule,
1234
+ noDebugRuleName,
1235
+ createNoDebugRule,
1236
+ noDeprecatedRuleName,
1237
+ createNoDeprecatedRule,
1238
+ noImportantRuleName,
1239
+ createNoImportantRule,
1240
+ noInvalidNestingRuleName,
1241
+ createNoInvalidNestingRule,
1242
+ noPrimitiveTokenRuleName,
1243
+ reportPrimitiveTokenViolations,
1244
+ createNoPrimitiveTokenRule,
1245
+ noMarginPropertiesRuleName,
1246
+ createNoMarginPropertiesRule,
1247
+ noPhysicalPropertiesRuleName,
1248
+ createNoPhysicalPropertiesRule,
1249
+ noShorthandLonghandMixRuleName,
1250
+ createNoShorthandLonghandMixRule,
1251
+ preferTokenRuleName,
1252
+ collectStrings,
1253
+ reportTokenViolations,
1254
+ createPreferTokenRule,
1255
+ preferTextStyleRuleName,
1256
+ createPreferTextStyleRule,
1257
+ pluginMeta,
1258
+ loadPandaProject,
1259
+ bindRules,
1260
+ createPandaPlugin
1261
+ };