@pragmatiks/lint 0.3.4 → 0.5.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.
@@ -0,0 +1,2610 @@
1
+ import css from '@eslint/css';
2
+ import reactX from 'eslint-plugin-react-x';
3
+ import { readFileSync, existsSync } from 'fs';
4
+ import path2 from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { defineConfig, globalIgnores } from 'eslint/config';
7
+ import sonarjs from 'eslint-plugin-sonarjs';
8
+ import unicorn from 'eslint-plugin-unicorn';
9
+ import tseslint from 'typescript-eslint';
10
+ import { createRequire } from 'module';
11
+ import { lexer, property, generate, toPlainObject, parse } from '@eslint/css-tree';
12
+
13
+ // src/eslint-config/next/index.ts
14
+
15
+ // src/eslint-config/options.ts
16
+ var COMPLEXITY_OPTS = 10;
17
+ var MAX_LINES_PER_FUNCTION_OPTS = {
18
+ max: 50,
19
+ skipBlankLines: true,
20
+ skipComments: true
21
+ };
22
+ var MAX_STATEMENTS_OPTS = 15;
23
+ var MAX_DEPTH_OPTS = 4;
24
+ var MAX_PARAMS_OPTS = 4;
25
+ var PREVENT_ABBREVIATIONS_OPTS = {
26
+ allowList: {
27
+ id: true,
28
+ url: true,
29
+ uri: true,
30
+ api: true,
31
+ cli: true,
32
+ sdk: true,
33
+ os: true,
34
+ io: true,
35
+ ip: true,
36
+ tls: true,
37
+ ssl: true,
38
+ jwt: true,
39
+ json: true,
40
+ yaml: true,
41
+ html: true,
42
+ css: true,
43
+ dom: true,
44
+ ast: true,
45
+ gpu: true,
46
+ cpu: true,
47
+ ram: true,
48
+ vm: true,
49
+ props: true,
50
+ ref: true,
51
+ e: true
52
+ }
53
+ };
54
+ var FILENAME_CASE_OPTIONS = {
55
+ cases: {
56
+ kebabCase: true,
57
+ pascalCase: true,
58
+ camelCase: true
59
+ }
60
+ };
61
+ var NO_USELESS_UNDEFINED_OPTIONS = { checkArguments: false };
62
+
63
+ // src/eslint-config/index.ts
64
+ var PRAGMATIKS_LINT_FILES = ["**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"];
65
+ function findPackageRoot(start) {
66
+ let current = start;
67
+ for (; ; ) {
68
+ if (existsSync(path2.join(current, "package.json"))) {
69
+ return current;
70
+ }
71
+ const parent = path2.dirname(current);
72
+ if (parent === current) {
73
+ throw new Error(`package.json not found above ${start}`);
74
+ }
75
+ current = parent;
76
+ }
77
+ }
78
+ function semgrepRulesDirectory() {
79
+ const here = path2.dirname(fileURLToPath(import.meta.url));
80
+ return path2.join(findPackageRoot(here), "src", "semgrep-rules");
81
+ }
82
+ function recommendedOrThrow(plugin, peer) {
83
+ const recommended = plugin.configs?.recommended;
84
+ if (!recommended) {
85
+ throw new Error(
86
+ `@pragmatiks/lint: ${peer.name} has no recommended flat-config preset (peer dependency major mismatch). To fix it, install ${peer.name} ${peer.range}.`
87
+ );
88
+ }
89
+ return { ...recommended, files: PRAGMATIKS_LINT_FILES };
90
+ }
91
+ function pragmatiksConfig() {
92
+ return defineConfig([
93
+ {
94
+ files: PRAGMATIKS_LINT_FILES,
95
+ languageOptions: {
96
+ parser: tseslint.parser,
97
+ parserOptions: {
98
+ ecmaVersion: "latest",
99
+ sourceType: "module"
100
+ }
101
+ },
102
+ settings: {
103
+ pragmatiksLint: {
104
+ semgrepRulesDirectory: semgrepRulesDirectory()
105
+ }
106
+ }
107
+ },
108
+ recommendedOrThrow(sonarjs, {
109
+ name: "eslint-plugin-sonarjs",
110
+ range: "^4.0.0"
111
+ }),
112
+ recommendedOrThrow(unicorn, {
113
+ name: "eslint-plugin-unicorn",
114
+ range: "^64.0.0"
115
+ }),
116
+ {
117
+ files: PRAGMATIKS_LINT_FILES,
118
+ rules: {
119
+ complexity: ["error", COMPLEXITY_OPTS],
120
+ "max-lines-per-function": ["error", MAX_LINES_PER_FUNCTION_OPTS],
121
+ "max-statements": ["error", MAX_STATEMENTS_OPTS],
122
+ "max-depth": ["error", MAX_DEPTH_OPTS],
123
+ "max-params": ["error", MAX_PARAMS_OPTS],
124
+ "unicorn/prevent-abbreviations": ["error", PREVENT_ABBREVIATIONS_OPTS]
125
+ }
126
+ },
127
+ globalIgnores(["dist/**", "node_modules/**", ".next/**", ".turbo/**"])
128
+ ]);
129
+ }
130
+ var CSS_WIDE_KEYWORDS = new Set(
131
+ lexer.cssWideKeywords
132
+ );
133
+ var TOP_CONTEXT = {
134
+ selectors: [],
135
+ layer: void 0,
136
+ inKeyframes: false,
137
+ inTheme: false,
138
+ block: void 0
139
+ };
140
+ var FOCUS_VARIANTS = /* @__PURE__ */ new Set(["focus", "focus-visible", "focus-within"]);
141
+ var stylesheetDeclarations = /* @__PURE__ */ new WeakMap();
142
+ function parseValue(text) {
143
+ let parsed;
144
+ try {
145
+ parsed = toPlainObject(
146
+ parse(text, { context: "value", parseCustomProperty: true })
147
+ );
148
+ } catch {
149
+ return { type: "Value", children: [{ type: "Raw", value: text }] };
150
+ }
151
+ if (parsed.type === "Value") {
152
+ return parsed;
153
+ }
154
+ return { type: "Value", children: [parsed] };
155
+ }
156
+ function normalizeProperty(property) {
157
+ return property.startsWith("--") ? property : property.toLowerCase();
158
+ }
159
+ function buildDeclaration(node, context) {
160
+ const text = node.value.type === "Raw" ? node.value.value.trim() : generate(node.value);
161
+ return {
162
+ property: normalizeProperty(node.property),
163
+ value: parseValue(text),
164
+ text,
165
+ important: Boolean(node.important),
166
+ ...context,
167
+ node
168
+ };
169
+ }
170
+ function layerName(node) {
171
+ if (!node.prelude) {
172
+ return "";
173
+ }
174
+ if (node.prelude.type === "Raw") {
175
+ return node.prelude.value.trim();
176
+ }
177
+ const [first] = node.prelude.children;
178
+ return first?.type === "Layer" ? first.name : generate(node.prelude);
179
+ }
180
+ function focusVariant(node) {
181
+ if (node.name.toLowerCase() !== "variant" || !node.prelude) {
182
+ return void 0;
183
+ }
184
+ const variant = generate(node.prelude).trim();
185
+ return FOCUS_VARIANTS.has(variant) ? variant : void 0;
186
+ }
187
+ function atruleContext(node, context) {
188
+ const name = node.name.toLowerCase();
189
+ if (name === "layer") {
190
+ const nested = layerName(node);
191
+ return {
192
+ ...context,
193
+ layer: context.layer === void 0 ? nested : `${context.layer}.${nested}`
194
+ };
195
+ }
196
+ if (name.endsWith("keyframes")) {
197
+ return { ...context, inKeyframes: true };
198
+ }
199
+ if (name === "theme") {
200
+ return { ...context, inTheme: true };
201
+ }
202
+ const variant = focusVariant(node);
203
+ if (variant === void 0) {
204
+ return context;
205
+ }
206
+ return {
207
+ ...context,
208
+ selectors: [...context.selectors, `&:${variant}`],
209
+ block: node
210
+ };
211
+ }
212
+ function collectDeclarations(nodes, context, output) {
213
+ for (const node of nodes) {
214
+ if (node.type === "Declaration") {
215
+ output.push(buildDeclaration(node, context));
216
+ } else if (node.type === "Rule") {
217
+ const selectors = [...context.selectors, generate(node.prelude)];
218
+ collectDeclarations(
219
+ node.block.children,
220
+ { ...context, selectors, block: node },
221
+ output
222
+ );
223
+ } else if (node.type === "Atrule" && node.block) {
224
+ collectDeclarations(
225
+ node.block.children,
226
+ atruleContext(node, context),
227
+ output
228
+ );
229
+ }
230
+ }
231
+ return output;
232
+ }
233
+ function collectStylesheetDeclarations(stylesheet) {
234
+ const cached = stylesheetDeclarations.get(stylesheet);
235
+ if (cached) {
236
+ return cached;
237
+ }
238
+ const declarations = collectDeclarations(
239
+ stylesheet.children,
240
+ TOP_CONTEXT,
241
+ []
242
+ );
243
+ stylesheetDeclarations.set(stylesheet, declarations);
244
+ return declarations;
245
+ }
246
+ function parseStylesheetDeclarations(css2) {
247
+ const stylesheet = toPlainObject(
248
+ parse(css2, { parseCustomProperty: true })
249
+ );
250
+ return collectStylesheetDeclarations(stylesheet);
251
+ }
252
+ function parseStyleLeaf(leaf) {
253
+ const { property, text } = leaf;
254
+ if (text === void 0) {
255
+ return void 0;
256
+ }
257
+ return {
258
+ property: normalizeProperty(property),
259
+ value: parseValue(text),
260
+ text,
261
+ important: false,
262
+ ...TOP_CONTEXT,
263
+ node: void 0
264
+ };
265
+ }
266
+ function listValueNodes(node) {
267
+ return [
268
+ node,
269
+ ...listCssChildren(node).flatMap((child) => listValueNodes(child))
270
+ ];
271
+ }
272
+ function listCssChildren(node) {
273
+ return "children" in node && Array.isArray(node.children) ? node.children : [];
274
+ }
275
+ function splitOnCommas(nodes) {
276
+ const segments = [[]];
277
+ for (const node of nodes) {
278
+ if (node.type === "Operator" && node.value === ",") {
279
+ segments.push([]);
280
+ } else {
281
+ segments.at(-1)?.push(node);
282
+ }
283
+ }
284
+ return segments;
285
+ }
286
+ function isVariableReference(node) {
287
+ return node?.type === "Function" && node.name.toLowerCase() === "var";
288
+ }
289
+ function firstIdentifierArgument(node) {
290
+ const [first] = node?.type === "Function" ? node.children : [];
291
+ return first?.type === "Identifier" ? first.name : void 0;
292
+ }
293
+ function referencedVariable(node) {
294
+ return isVariableReference(node) ? firstIdentifierArgument(node) : void 0;
295
+ }
296
+
297
+ // src/eslint-config/next/frontend/design-system.ts
298
+ var DEFAULT_THEME_OPTION = 4;
299
+ var CACHE_LIMIT = 1e4;
300
+ var SUPPORTED_TAILWIND_RANGE = "~4.1.17";
301
+ var VERSION_RECOVERY = `pin tailwindcss, @tailwindcss/postcss and @tailwindcss/node to the same ${SUPPORTED_TAILWIND_RANGE} version`;
302
+ var CANARY_EXPECTATIONS = [
303
+ { token: "z-60", property: "z-index", value: "60" },
304
+ { token: "h-screen", property: "height", value: "100vh" },
305
+ { token: "transition-all", property: "transition-property", value: "all" },
306
+ { token: "bg-[#fff]", property: "background-color", value: "#fff" },
307
+ {
308
+ token: "focus-visible:outline-hidden",
309
+ property: "outline-style",
310
+ value: "none"
311
+ }
312
+ ];
313
+ function loadManifest(directory) {
314
+ try {
315
+ return JSON.parse(
316
+ readFileSync(path2.join(directory, "package.json"), "utf8")
317
+ );
318
+ } catch {
319
+ return void 0;
320
+ }
321
+ }
322
+ function findPackageManifest(start, name) {
323
+ let current = start;
324
+ for (; ; ) {
325
+ const manifest = loadManifest(current);
326
+ if (manifest?.name === name) {
327
+ return manifest;
328
+ }
329
+ const parent = path2.dirname(current);
330
+ if (parent === current) {
331
+ throw new Error(
332
+ `@pragmatiks/lint: no package.json of ${name} above ${start}. To fix it, reinstall ${name}.`
333
+ );
334
+ }
335
+ current = parent;
336
+ }
337
+ }
338
+ function resolvePackageEntry(fromFile, name) {
339
+ try {
340
+ return createRequire(fromFile).resolve(name);
341
+ } catch {
342
+ throw new Error(
343
+ `@pragmatiks/lint: ${name} cannot be resolved from ${path2.dirname(fromFile)}. To fix it, install ${name} at ${SUPPORTED_TAILWIND_RANGE} in the app root.`
344
+ );
345
+ }
346
+ }
347
+ function readPackageVersion(fromFile, name) {
348
+ const entry = resolvePackageEntry(fromFile, name);
349
+ const version = findPackageManifest(path2.dirname(entry), name).version;
350
+ if (!version) {
351
+ throw new Error(
352
+ `@pragmatiks/lint: the package.json of ${name} has no version. To fix it, reinstall ${name}.`
353
+ );
354
+ }
355
+ return version;
356
+ }
357
+ function buildCandidateValue(candidate) {
358
+ const value = candidate.value;
359
+ if (typeof value === "string") {
360
+ return { valueKind: "arbitrary", value };
361
+ }
362
+ if (!value?.kind) {
363
+ return { valueKind: "none", value: void 0 };
364
+ }
365
+ return {
366
+ valueKind: value.kind === "arbitrary" ? "arbitrary" : "named",
367
+ value: value.value
368
+ };
369
+ }
370
+ function buildArbitraryInputs(candidate, value) {
371
+ const inputs = [];
372
+ if (value.valueKind === "arbitrary" && value.value !== void 0) {
373
+ inputs.push(value.value);
374
+ }
375
+ if (candidate.modifier?.kind === "arbitrary") {
376
+ inputs.push(candidate.modifier.value);
377
+ }
378
+ return inputs;
379
+ }
380
+ function buildClassCandidate(designSystem, candidate) {
381
+ const value = buildCandidateValue(candidate);
382
+ return {
383
+ utility: candidate.kind === "arbitrary" ? candidate.property ?? "" : candidate.root ?? "",
384
+ arbitraryProperty: candidate.kind === "arbitrary",
385
+ variants: candidate.variants.map(
386
+ (variant) => designSystem.printVariant(variant)
387
+ ),
388
+ important: candidate.important,
389
+ ...value,
390
+ modifierKind: candidate.modifier?.kind ?? "none",
391
+ arbitraryInputs: buildArbitraryInputs(candidate, value)
392
+ };
393
+ }
394
+ function collectProperties(nodes, output) {
395
+ for (const node of nodes ?? []) {
396
+ if (node.kind === "declaration" && node.property) {
397
+ output.add(node.property);
398
+ }
399
+ collectProperties(node.nodes, output);
400
+ }
401
+ return output;
402
+ }
403
+ function memoize(compute) {
404
+ const cache = /* @__PURE__ */ new Map();
405
+ return (key) => {
406
+ const result = cache.has(key) ? cache.get(key) : compute(key);
407
+ cache.delete(key);
408
+ cache.set(key, result);
409
+ if (cache.size > CACHE_LIMIT) {
410
+ cache.delete(cache.keys().next().value);
411
+ }
412
+ return result;
413
+ };
414
+ }
415
+ function createDesignSystemView(tailwind) {
416
+ const keyframes = /* @__PURE__ */ new Map();
417
+ for (const block of tailwind.theme.getKeyframes()) {
418
+ if (block.params) {
419
+ keyframes.set(block.params, [
420
+ ...collectProperties(block.nodes, /* @__PURE__ */ new Set())
421
+ ]);
422
+ }
423
+ }
424
+ return {
425
+ declarationsOf: memoize((token) => {
426
+ const css2 = tailwind.candidatesToCss([token])[0];
427
+ return css2 ? parseStylesheetDeclarations(css2) : void 0;
428
+ }),
429
+ candidatesOf: memoize(
430
+ (token) => tailwind.parseCandidate(token).map((candidate) => buildClassCandidate(tailwind, candidate))
431
+ ),
432
+ keyframeProperties: (name) => keyframes.get(name),
433
+ isDefaultThemeVariable: (variableName) => (tailwind.theme.getOptions(variableName) & DEFAULT_THEME_OPTION) !== 0,
434
+ themeValue: (variableName) => tailwind.resolveThemeValue(variableName)
435
+ };
436
+ }
437
+ function assertMatchingVersions(rootDirectory) {
438
+ const projectManifest = path2.join(rootDirectory, "package.json");
439
+ const versions = {
440
+ tailwindcss: readPackageVersion(projectManifest, "tailwindcss"),
441
+ "@tailwindcss/postcss": readPackageVersion(
442
+ projectManifest,
443
+ "@tailwindcss/postcss"
444
+ ),
445
+ "@tailwindcss/node": readPackageVersion(
446
+ fileURLToPath(import.meta.url),
447
+ "@tailwindcss/node"
448
+ )
449
+ };
450
+ if (new Set(Object.values(versions)).size !== 1) {
451
+ throw new Error(
452
+ `@pragmatiks/lint: tailwindcss, @tailwindcss/postcss and @tailwindcss/node need one Tailwind version; found ${JSON.stringify(versions)}. To fix it, ${VERSION_RECOVERY}.`
453
+ );
454
+ }
455
+ }
456
+ function assertTailwindApi(designSystem) {
457
+ const [candidate] = designSystem.parseCandidate(
458
+ "focus-visible:outline-hidden"
459
+ );
460
+ const usable = typeof designSystem.theme.getOptions === "function" && typeof designSystem.theme.getKeyframes === "function" && typeof designSystem.printVariant === "function" && typeof designSystem.resolveThemeValue === "function" && Array.isArray(candidate?.variants) && typeof candidate.important === "boolean" && typeof candidate.root === "string";
461
+ if (!usable) {
462
+ throw new Error(
463
+ `@pragmatiks/lint: the canary found that the Tailwind design system API changed shape. To fix it, ${VERSION_RECOVERY}, the range @pragmatiks/lint supports.`
464
+ );
465
+ }
466
+ }
467
+ function assertCanary(view) {
468
+ for (const expected of CANARY_EXPECTATIONS) {
469
+ const found = view.declarationsOf(expected.token)?.some(
470
+ (declaration) => declaration.property === expected.property && declaration.text === expected.value
471
+ );
472
+ if (!found) {
473
+ throw new Error(
474
+ `@pragmatiks/lint: the canary found that ${expected.token} no longer compiles to ${expected.property}: ${expected.value}. To fix it, ${VERSION_RECOVERY}, the range @pragmatiks/lint supports.`
475
+ );
476
+ }
477
+ }
478
+ }
479
+ async function loadDesignSystem(source) {
480
+ assertMatchingVersions(source.rootDir);
481
+ const { __unstable__loadDesignSystem } = await import('@tailwindcss/node');
482
+ const stylesheetPath = path2.resolve(
483
+ source.rootDir,
484
+ source.tailwindStylesheet
485
+ );
486
+ const stylesheet = readFileSync(stylesheetPath, "utf8");
487
+ const loaded = await __unstable__loadDesignSystem(stylesheet, {
488
+ base: path2.dirname(stylesheetPath)
489
+ });
490
+ const designSystem = loaded;
491
+ assertTailwindApi(designSystem);
492
+ const view = createDesignSystemView(designSystem);
493
+ assertCanary(view);
494
+ return view;
495
+ }
496
+
497
+ // src/eslint-config/next/frontend/class-group-declarations.ts
498
+ function compileClassGroup(group, view) {
499
+ return group.tokens.flatMap((token) => {
500
+ const declarations = view.declarationsOf(token);
501
+ return declarations ? [{ token, declarations }] : [];
502
+ });
503
+ }
504
+
505
+ // src/eslint-config/next/frontend/ast.ts
506
+ var FUNCTION_TYPES = /* @__PURE__ */ new Set([
507
+ "ArrowFunctionExpression",
508
+ "FunctionExpression",
509
+ "FunctionDeclaration"
510
+ ]);
511
+ function toRuleNode(node) {
512
+ return node;
513
+ }
514
+ function toAstNode(node) {
515
+ return node;
516
+ }
517
+ function childNode(node, name) {
518
+ const value = node?.[name];
519
+ return value && typeof value === "object" && "type" in value ? value : void 0;
520
+ }
521
+ function childNodes(node, name) {
522
+ const value = node?.[name];
523
+ return Array.isArray(value) ? value : [];
524
+ }
525
+ function childString(node, name) {
526
+ const value = node?.[name];
527
+ return typeof value === "string" ? value : void 0;
528
+ }
529
+ function hasCookedText(value) {
530
+ return typeof value === "object" && value !== null && "cooked" in value && typeof value.cooked === "string";
531
+ }
532
+ function cookedText(quasi) {
533
+ const value = quasi?.value;
534
+ return hasCookedText(value) ? value.cooked : "";
535
+ }
536
+ function keyName(property) {
537
+ const key = childNode(property, "key");
538
+ if (!property.computed && key?.type === "Identifier") {
539
+ return childString(key, "name");
540
+ }
541
+ return key?.type === "Literal" ? childString(key, "value") : void 0;
542
+ }
543
+ function attributeName(attribute) {
544
+ return childString(childNode(attribute, "name"), "name");
545
+ }
546
+ function attributeExpression(attribute) {
547
+ const value = childNode(attribute, "value");
548
+ return value?.type === "JSXExpressionContainer" ? childNode(value, "expression") : void 0;
549
+ }
550
+ function jsxElementName(opening) {
551
+ const name = childNode(opening, "name");
552
+ return name?.type === "JSXIdentifier" ? childString(name, "name") : void 0;
553
+ }
554
+ function findJsxAttribute(opening, name) {
555
+ return childNodes(opening, "attributes").find(
556
+ (attribute) => attribute.type === "JSXAttribute" && attributeName(attribute) === name
557
+ );
558
+ }
559
+ function jsxAttributeValue(attribute) {
560
+ return attributeExpression(attribute) ?? childNode(attribute, "value");
561
+ }
562
+ function staticText(value) {
563
+ if (value?.type === "Literal" && (typeof value.value === "string" || typeof value.value === "number")) {
564
+ return String(value.value);
565
+ }
566
+ if (value?.type === "TemplateLiteral" && childNodes(value, "expressions").length === 0) {
567
+ return cookedText(childNodes(value, "quasis")[0]);
568
+ }
569
+ return void 0;
570
+ }
571
+ function findVariable(scope, name) {
572
+ for (let current = scope; current; current = current.upper) {
573
+ const variable = current.set.get(name);
574
+ if (variable) {
575
+ return variable;
576
+ }
577
+ }
578
+ return void 0;
579
+ }
580
+ function containsNode(outer, inner) {
581
+ const [outerStart, outerEnd] = outer.range ?? [0, -1];
582
+ const [innerStart, innerEnd] = inner.range ?? [0, -1];
583
+ return innerStart >= outerStart && innerEnd <= outerEnd;
584
+ }
585
+
586
+ // src/eslint-config/next/frontend/collect.ts
587
+ var MOTION_ATTRIBUTE = /^(?:initial|animate|exit|variants|while[A-Z]\w*)$/;
588
+ var EVENT_ATTRIBUTE = /^on[A-Z]/;
589
+ var CLASS_ATTRIBUTE = /class(?:name)?s?$/i;
590
+ var EXPRESSION_WRAPPERS = /* @__PURE__ */ new Set([
591
+ "TSAsExpression",
592
+ "TSSatisfiesExpression",
593
+ "TSNonNullExpression",
594
+ "ParenthesizedExpression"
595
+ ]);
596
+ var CLASS_SOURCE_BOUNDARIES = /* @__PURE__ */ new Set([
597
+ "CallExpression",
598
+ "TaggedTemplateExpression",
599
+ "JSXElement",
600
+ "JSXFragment",
601
+ ...FUNCTION_TYPES
602
+ ]);
603
+ var SOURCE_PARENTS = /* @__PURE__ */ new Set([
604
+ "ImportDeclaration",
605
+ "ExportNamedDeclaration",
606
+ "ExportAllDeclaration",
607
+ "ImportExpression"
608
+ ]);
609
+ var TEMPLATE_BOUNDARY = "\0";
610
+ var models = /* @__PURE__ */ new WeakMap();
611
+ function computeCssPropertyName(key) {
612
+ if (key.startsWith("--")) {
613
+ return key;
614
+ }
615
+ const kebab = key.replaceAll(
616
+ /[A-Z]/g,
617
+ (letter) => `-${letter.toLowerCase()}`
618
+ );
619
+ return kebab.startsWith("ms-") ? `-${kebab}` : kebab;
620
+ }
621
+ function isClassHelperKey(parent) {
622
+ const object = parent.parent;
623
+ const call = object?.parent;
624
+ return parent.type === "Property" && object?.type === "ObjectExpression" && call?.type === "CallExpression" && childNodes(call, "arguments").includes(object);
625
+ }
626
+ function isNonClassAttributeValue(node) {
627
+ for (let current = node.parent; current; current = current.parent) {
628
+ if (current.type === "JSXAttribute") {
629
+ return !CLASS_ATTRIBUTE.test(attributeName(current) ?? "");
630
+ }
631
+ if (CLASS_SOURCE_BOUNDARIES.has(current.type)) {
632
+ return false;
633
+ }
634
+ }
635
+ return false;
636
+ }
637
+ function isExcludedLiteral(node) {
638
+ const parent = node.parent;
639
+ if (!parent) {
640
+ return true;
641
+ }
642
+ return SOURCE_PARENTS.has(parent.type) && parent.source === node || parent.type === "ExpressionStatement" && typeof parent.directive === "string" || parent.key === node && !parent.computed && !isClassHelperKey(parent) || parent.type === "TSLiteralType" || isNonClassAttributeValue(node);
643
+ }
644
+ function enclosingElement(node) {
645
+ for (let current = node.parent; current; current = current.parent) {
646
+ if (current.type === "JSXAttribute") {
647
+ return current.parent;
648
+ }
649
+ }
650
+ return void 0;
651
+ }
652
+ function templateTokens(node) {
653
+ const text = childNodes(node, "quasis").map((quasi) => cookedText(quasi)).join(TEMPLATE_BOUNDARY);
654
+ return text.split(/\s+/).filter((token) => token !== "" && !token.includes(TEMPLATE_BOUNDARY));
655
+ }
656
+ function collectClassGroup(node, model) {
657
+ if (isExcludedLiteral(node)) {
658
+ return;
659
+ }
660
+ const tokens = node.type === "Literal" ? String(node.value).split(/\s+/).filter(Boolean) : templateTokens(node);
661
+ if (tokens.length > 0) {
662
+ model.classGroups.push({ node, tokens, element: enclosingElement(node) });
663
+ }
664
+ }
665
+ function unwrapExpression(node) {
666
+ let current = node;
667
+ while (current && EXPRESSION_WRAPPERS.has(current.type)) {
668
+ current = childNode(current, "expression");
669
+ }
670
+ return current;
671
+ }
672
+ function listBranches(node) {
673
+ const expression = unwrapExpression(node);
674
+ if (expression?.type === "ConditionalExpression") {
675
+ return [
676
+ ...listBranches(childNode(expression, "consequent")),
677
+ ...listBranches(childNode(expression, "alternate"))
678
+ ];
679
+ }
680
+ if (expression?.type === "LogicalExpression") {
681
+ return [
682
+ ...listBranches(childNode(expression, "left")),
683
+ ...listBranches(childNode(expression, "right"))
684
+ ];
685
+ }
686
+ return expression ? [expression] : [];
687
+ }
688
+ function listObjectKeys(expression) {
689
+ return listBranches(expression).filter((branch) => branch.type === "ObjectExpression").flatMap((object) => childNodes(object, "properties")).flatMap((property) => {
690
+ const key = property.type === "Property" ? keyName(property) : void 0;
691
+ return key === void 0 ? [] : [{ property, key }];
692
+ });
693
+ }
694
+ function listStaticTexts(value) {
695
+ return listBranches(value).flatMap((branch) => {
696
+ const text = staticText(branch);
697
+ return text === void 0 ? [] : [text];
698
+ });
699
+ }
700
+ function listKeyValues(keys, element) {
701
+ return keys.flatMap(({ property, key }) => {
702
+ const texts = listStaticTexts(childNode(property, "value"));
703
+ const values = texts.length > 0 ? texts : [void 0];
704
+ return values.map((text) => ({
705
+ node: property,
706
+ property: computeCssPropertyName(key),
707
+ text,
708
+ element
709
+ }));
710
+ });
711
+ }
712
+ function collectStyleLeaves(attribute, model) {
713
+ const keys = listObjectKeys(attributeExpression(attribute));
714
+ model.styleLeaves.push(...listKeyValues(keys, attribute.parent));
715
+ }
716
+ function listVariantGroups(entries) {
717
+ return entries.map(({ property, key }) => ({
718
+ keys: listObjectKeys(childNode(property, "value")),
719
+ variantLabel: key
720
+ }));
721
+ }
722
+ function collectMotionKeys(attribute, name, model) {
723
+ const keys = listObjectKeys(attributeExpression(attribute));
724
+ const groups = name === "variants" ? listVariantGroups(keys) : [{ keys, variantLabel: void 0 }];
725
+ const motionKeys = groups.flatMap(
726
+ (group) => listKeyValues(group.keys, attribute.parent).map((leaf) => ({
727
+ ...leaf,
728
+ attributeName: name,
729
+ variantLabel: group.variantLabel
730
+ }))
731
+ );
732
+ model.motionKeys.push(...motionKeys);
733
+ }
734
+ function isUseCallback(callee) {
735
+ if (callee?.type === "Identifier") {
736
+ return callee.name === "useCallback";
737
+ }
738
+ return callee?.type === "MemberExpression" && childString(childNode(callee, "property"), "name") === "useCallback";
739
+ }
740
+ function bodyExpression(body) {
741
+ if (body?.type !== "BlockStatement") {
742
+ return body;
743
+ }
744
+ const statements = childNodes(body, "body");
745
+ const [only] = statements;
746
+ return statements.length === 1 && only?.type === "ExpressionStatement" ? childNode(only, "expression") : void 0;
747
+ }
748
+ function delegateCallee(handler) {
749
+ const call = bodyExpression(childNode(handler, "body"));
750
+ const callee = call?.type === "CallExpression" ? childNode(call, "callee") : void 0;
751
+ return callee?.type === "Identifier" ? callee : void 0;
752
+ }
753
+ function definitionExpression(definition) {
754
+ if (definition?.type === "FunctionName") {
755
+ return toAstNode(definition.node);
756
+ }
757
+ return definition?.type === "Variable" && definition.parent.kind === "const" ? childNode(toAstNode(definition.node), "init") : void 0;
758
+ }
759
+ function resolveHandlerFunctions(expression, sourceCode, seen) {
760
+ if (!expression || seen.has(expression)) {
761
+ return [];
762
+ }
763
+ seen.add(expression);
764
+ if (FUNCTION_TYPES.has(expression.type)) {
765
+ const delegates = resolveHandlerFunctions(
766
+ delegateCallee(expression),
767
+ sourceCode,
768
+ seen
769
+ );
770
+ return [expression, ...delegates];
771
+ }
772
+ if (expression.type === "CallExpression" && isUseCallback(childNode(expression, "callee"))) {
773
+ return resolveHandlerFunctions(
774
+ childNodes(expression, "arguments")[0],
775
+ sourceCode,
776
+ seen
777
+ );
778
+ }
779
+ if (expression.type !== "Identifier") {
780
+ return [];
781
+ }
782
+ const scope = sourceCode.getScope(toRuleNode(expression));
783
+ const definition = findVariable(scope, String(expression.name))?.defs[0];
784
+ return resolveHandlerFunctions(
785
+ definitionExpression(definition),
786
+ sourceCode,
787
+ seen
788
+ );
789
+ }
790
+ function isOpaqueAttribute(node) {
791
+ if (node.type !== "JSXAttribute") {
792
+ return false;
793
+ }
794
+ const name = attributeName(node) ?? "";
795
+ return name === "style" || MOTION_ATTRIBUTE.test(name);
796
+ }
797
+ function collectAttribute(attribute, model, sourceCode) {
798
+ const name = attributeName(attribute) ?? "";
799
+ if (name === "style") {
800
+ collectStyleLeaves(attribute, model);
801
+ return;
802
+ }
803
+ if (MOTION_ATTRIBUTE.test(name)) {
804
+ collectMotionKeys(attribute, name, model);
805
+ return;
806
+ }
807
+ if (!EVENT_ATTRIBUTE.test(name) || !attribute.parent) {
808
+ return;
809
+ }
810
+ const handlerFunctions = resolveHandlerFunctions(
811
+ attributeExpression(attribute),
812
+ sourceCode,
813
+ /* @__PURE__ */ new Set()
814
+ );
815
+ if (handlerFunctions.length > 0) {
816
+ model.pendingHandlers.push({
817
+ element: attribute.parent,
818
+ attributeName: name,
819
+ attribute,
820
+ handlerFunctions
821
+ });
822
+ }
823
+ }
824
+ function collectStyleAssignment(node, model) {
825
+ const target = childNode(node, "left");
826
+ const object = childNode(target, "object");
827
+ const isStyle = target?.type === "MemberExpression" && object?.type === "MemberExpression" && !object.computed && childString(childNode(object, "property"), "name") === "style";
828
+ if (!isStyle || !target) {
829
+ return;
830
+ }
831
+ const property = childNode(target, "property");
832
+ const name = target.computed ? childString(property, "value") : childString(property, "name");
833
+ if (name !== void 0) {
834
+ model.styleAssignments.push({
835
+ node,
836
+ property: computeCssPropertyName(name)
837
+ });
838
+ }
839
+ }
840
+ function collectNode(node, model, sourceCode) {
841
+ if (node.type === "JSXAttribute") {
842
+ collectAttribute(node, model, sourceCode);
843
+ } else if (node.type === "Literal" && typeof node.value === "string" || node.type === "TemplateLiteral") {
844
+ collectClassGroup(node, model);
845
+ } else if (node.type === "AssignmentExpression") {
846
+ collectStyleAssignment(node, model);
847
+ }
848
+ }
849
+ function traverse(node, keys, visit) {
850
+ if (!visit(node)) {
851
+ return;
852
+ }
853
+ for (const key of keys[node.type] ?? []) {
854
+ const child = node[key];
855
+ const children = Array.isArray(child) ? child : [child];
856
+ for (const item of children) {
857
+ if (item && typeof item === "object" && "type" in item) {
858
+ traverse(item, keys, visit);
859
+ }
860
+ }
861
+ }
862
+ }
863
+ function selectContainedAssignments(functions, assignments) {
864
+ return assignments.filter(
865
+ (assignment) => functions.some((container) => containsNode(container, assignment.node))
866
+ );
867
+ }
868
+ function collectFrontendModel(sourceCode) {
869
+ const cached = models.get(sourceCode.ast);
870
+ if (cached) {
871
+ return cached;
872
+ }
873
+ const model = {
874
+ classGroups: [],
875
+ styleLeaves: [],
876
+ motionKeys: [],
877
+ styleAssignments: [],
878
+ pendingHandlers: []
879
+ };
880
+ traverse(toAstNode(sourceCode.ast), sourceCode.visitorKeys, (node) => {
881
+ collectNode(node, model, sourceCode);
882
+ return !isOpaqueAttribute(node);
883
+ });
884
+ const { pendingHandlers, ...collected } = model;
885
+ const handlers = pendingHandlers.map(({ handlerFunctions, ...handler }) => ({
886
+ ...handler,
887
+ styleAssignments: selectContainedAssignments(
888
+ handlerFunctions,
889
+ collected.styleAssignments
890
+ )
891
+ }));
892
+ const result = { ...collected, handlers };
893
+ models.set(sourceCode.ast, result);
894
+ return result;
895
+ }
896
+ var MOTION_MESSAGES = {
897
+ all: "{{source}} transitions all properties; list the properties it animates, such as transform, opacity or filter.",
898
+ implicitAll: "{{source}} has a transition layer without a property, which animates all properties; name the property.",
899
+ layout: "{{source}} animates the layout property {{property}}; animate transform, opacity or filter instead.",
900
+ unverifiable: "{{source}} animates a property that cannot be verified statically; name a known property."
901
+ };
902
+ var LAYOUT_PROPERTIES = /* @__PURE__ */ new Set([
903
+ "width",
904
+ "height",
905
+ "min-width",
906
+ "min-height",
907
+ "max-width",
908
+ "max-height",
909
+ "block-size",
910
+ "inline-size",
911
+ "min-block-size",
912
+ "min-inline-size",
913
+ "max-block-size",
914
+ "max-inline-size",
915
+ "aspect-ratio",
916
+ "contain-intrinsic-size",
917
+ "contain-intrinsic-width",
918
+ "contain-intrinsic-height",
919
+ "contain-intrinsic-block-size",
920
+ "contain-intrinsic-inline-size",
921
+ "inset",
922
+ "inset-block",
923
+ "inset-block-start",
924
+ "inset-block-end",
925
+ "inset-inline",
926
+ "inset-inline-start",
927
+ "inset-inline-end",
928
+ "top",
929
+ "right",
930
+ "bottom",
931
+ "left",
932
+ "margin",
933
+ "margin-top",
934
+ "margin-right",
935
+ "margin-bottom",
936
+ "margin-left",
937
+ "margin-block",
938
+ "margin-block-start",
939
+ "margin-block-end",
940
+ "margin-inline",
941
+ "margin-inline-start",
942
+ "margin-inline-end",
943
+ "margin-trim",
944
+ "padding",
945
+ "padding-top",
946
+ "padding-right",
947
+ "padding-bottom",
948
+ "padding-left",
949
+ "padding-block",
950
+ "padding-block-start",
951
+ "padding-block-end",
952
+ "padding-inline",
953
+ "padding-inline-start",
954
+ "padding-inline-end",
955
+ "border",
956
+ "border-width",
957
+ "border-top",
958
+ "border-right",
959
+ "border-bottom",
960
+ "border-left",
961
+ "border-top-width",
962
+ "border-right-width",
963
+ "border-bottom-width",
964
+ "border-left-width",
965
+ "border-block",
966
+ "border-block-width",
967
+ "border-block-start",
968
+ "border-block-start-width",
969
+ "border-block-end",
970
+ "border-block-end-width",
971
+ "border-inline",
972
+ "border-inline-width",
973
+ "border-inline-start",
974
+ "border-inline-start-width",
975
+ "border-inline-end",
976
+ "border-inline-end-width",
977
+ "border-spacing",
978
+ "gap",
979
+ "row-gap",
980
+ "column-gap",
981
+ "grid-gap",
982
+ "grid-row-gap",
983
+ "grid-column-gap",
984
+ "grid",
985
+ "grid-area",
986
+ "grid-auto-columns",
987
+ "grid-auto-flow",
988
+ "grid-auto-rows",
989
+ "grid-column",
990
+ "grid-column-start",
991
+ "grid-column-end",
992
+ "grid-row",
993
+ "grid-row-start",
994
+ "grid-row-end",
995
+ "grid-template",
996
+ "grid-template-areas",
997
+ "grid-template-columns",
998
+ "grid-template-rows",
999
+ "flex",
1000
+ "flex-basis",
1001
+ "flex-direction",
1002
+ "flex-flow",
1003
+ "flex-grow",
1004
+ "flex-line-count",
1005
+ "flex-shrink",
1006
+ "flex-wrap",
1007
+ "order",
1008
+ "columns",
1009
+ "column-count",
1010
+ "column-fill",
1011
+ "column-height",
1012
+ "column-span",
1013
+ "column-width",
1014
+ "column-wrap",
1015
+ "font",
1016
+ "font-family",
1017
+ "font-feature-settings",
1018
+ "font-kerning",
1019
+ "font-language-override",
1020
+ "font-optical-sizing",
1021
+ "font-size",
1022
+ "font-size-adjust",
1023
+ "font-stretch",
1024
+ "font-style",
1025
+ "font-synthesis",
1026
+ "font-synthesis-position",
1027
+ "font-synthesis-small-caps",
1028
+ "font-synthesis-style",
1029
+ "font-synthesis-weight",
1030
+ "font-variant",
1031
+ "font-variant-alternates",
1032
+ "font-variant-caps",
1033
+ "font-variant-east-asian",
1034
+ "font-variant-emoji",
1035
+ "font-variant-ligatures",
1036
+ "font-variant-numeric",
1037
+ "font-variant-position",
1038
+ "font-variation-settings",
1039
+ "font-weight",
1040
+ "font-width",
1041
+ "line-height",
1042
+ "letter-spacing",
1043
+ "word-spacing",
1044
+ "text-indent",
1045
+ "tab-size",
1046
+ "vertical-align",
1047
+ "scrollbar-width",
1048
+ "zoom"
1049
+ ]);
1050
+ var TRANSITION_KEYWORDS = /* @__PURE__ */ new Set([
1051
+ "ease",
1052
+ "ease-in",
1053
+ "ease-out",
1054
+ "ease-in-out",
1055
+ "linear",
1056
+ "step-start",
1057
+ "step-end",
1058
+ "normal",
1059
+ "allow-discrete"
1060
+ ]);
1061
+ var THEME_ANIMATION = /^--animate-/;
1062
+ function isLayoutProperty(property$1) {
1063
+ return LAYOUT_PROPERTIES.has(property(property$1).basename);
1064
+ }
1065
+ function classifyPropertyName(name) {
1066
+ const lower = name.toLowerCase();
1067
+ if (name.startsWith("--") || lower === "none" || CSS_WIDE_KEYWORDS.has(lower)) {
1068
+ return void 0;
1069
+ }
1070
+ if (lower === "all") {
1071
+ return { kind: "all", property: lower };
1072
+ }
1073
+ if (lexer.checkPropertyName(lower) !== void 0) {
1074
+ return { kind: "unverifiable", property: lower };
1075
+ }
1076
+ return isLayoutProperty(lower) ? { kind: "layout", property: lower } : void 0;
1077
+ }
1078
+ function classifyTransitionLayer(layer) {
1079
+ const rest = layer.filter((node) => !isVariableReference(node));
1080
+ const stripped = rest.length !== layer.length;
1081
+ if (rest.length > 0 && lexer.matchProperty("transition", { type: "Value", children: rest }).error) {
1082
+ return { kind: "unverifiable", property: void 0 };
1083
+ }
1084
+ const names = rest.flatMap(
1085
+ (node) => node.type === "Identifier" && !TRANSITION_KEYWORDS.has(node.name.toLowerCase()) ? [node.name] : []
1086
+ );
1087
+ const [name] = names;
1088
+ if (name === void 0) {
1089
+ return stripped ? { kind: "unverifiable", property: void 0 } : { kind: "implicitAll", property: void 0 };
1090
+ }
1091
+ return classifyPropertyName(name);
1092
+ }
1093
+ function classifyPropertyItem(item) {
1094
+ const [only, ...rest] = item;
1095
+ if (rest.length > 0 || only?.type !== "Identifier") {
1096
+ return { kind: "unverifiable", property: void 0 };
1097
+ }
1098
+ return classifyPropertyName(only.name);
1099
+ }
1100
+ function isCssWideKeywordValue(value) {
1101
+ const [only, ...rest] = value.children;
1102
+ return rest.length === 0 && only?.type === "Identifier" && CSS_WIDE_KEYWORDS.has(only.name.toLowerCase());
1103
+ }
1104
+ function isPresent(finding) {
1105
+ return finding !== void 0;
1106
+ }
1107
+ function evaluateTransition(declaration) {
1108
+ const basename = property(declaration.property).basename;
1109
+ if (basename !== "transition" && basename !== "transition-property" || isCssWideKeywordValue(declaration.value)) {
1110
+ return [];
1111
+ }
1112
+ const layers = splitOnCommas(declaration.value.children);
1113
+ const classify = basename === "transition" ? classifyTransitionLayer : classifyPropertyItem;
1114
+ return layers.map((layer) => classify(layer)).filter((finding) => isPresent(finding));
1115
+ }
1116
+ function evaluateKeyframeDeclaration(declaration) {
1117
+ return declaration.inKeyframes && isLayoutProperty(declaration.property) ? [{ kind: "layout", property: declaration.property }] : [];
1118
+ }
1119
+ function resolveThemeAnimationValues(value, view) {
1120
+ return value.children.flatMap((node) => {
1121
+ const name = referencedVariable(node);
1122
+ if (name === void 0 || !THEME_ANIMATION.test(name)) {
1123
+ return [];
1124
+ }
1125
+ const resolved = view.themeValue(name);
1126
+ return resolved === void 0 ? [] : [parseValue(resolved)];
1127
+ });
1128
+ }
1129
+ function evaluateAnimation(declaration, view) {
1130
+ const basename = property(declaration.property).basename;
1131
+ if (basename !== "animation" && basename !== "animation-name") {
1132
+ return [];
1133
+ }
1134
+ const values = [
1135
+ declaration.value,
1136
+ ...resolveThemeAnimationValues(declaration.value, view)
1137
+ ];
1138
+ const properties = values.flatMap((value) => value.children).flatMap((node) => {
1139
+ const name = animationName(node);
1140
+ return name === void 0 ? [] : view.keyframeProperties(name) ?? [];
1141
+ });
1142
+ return [...new Set(properties)].filter((property) => isLayoutProperty(property)).map((property) => ({ kind: "layout", property }));
1143
+ }
1144
+ function animationName(node) {
1145
+ if (node.type === "Identifier") {
1146
+ return node.name;
1147
+ }
1148
+ return node.type === "String" ? node.value : void 0;
1149
+ }
1150
+ function evaluateMotionKey(property) {
1151
+ return isLayoutProperty(property) ? [{ kind: "layout", property }] : [];
1152
+ }
1153
+ function reportMotionFindings(report, node, source, findings) {
1154
+ for (const finding of findings) {
1155
+ report({
1156
+ node,
1157
+ messageId: finding.kind,
1158
+ data: { source, property: finding.property ?? "" }
1159
+ });
1160
+ }
1161
+ }
1162
+
1163
+ // src/eslint-config/next/frontend/report.ts
1164
+ function createUniqueReporter(context) {
1165
+ const reported = /* @__PURE__ */ new Map();
1166
+ return ({ node, messageId, data }) => {
1167
+ const key = JSON.stringify([messageId, data ?? {}]);
1168
+ const keys = reported.get(node) ?? /* @__PURE__ */ new Set();
1169
+ if (keys.has(key)) {
1170
+ return;
1171
+ }
1172
+ keys.add(key);
1173
+ reported.set(node, keys);
1174
+ context.report({ node: toRuleNode(node), messageId, data });
1175
+ };
1176
+ }
1177
+
1178
+ // src/eslint-config/next/frontend/rules/animated-properties.ts
1179
+ function evaluateDeclarationMotion(declaration, view) {
1180
+ return declaration ? [
1181
+ ...evaluateTransition(declaration),
1182
+ ...evaluateAnimation(declaration, view)
1183
+ ] : [];
1184
+ }
1185
+ function reportModelMotion(report, model, view) {
1186
+ for (const group of model.classGroups) {
1187
+ for (const { token, declarations } of compileClassGroup(group, view)) {
1188
+ const findings = declarations.flatMap(
1189
+ (declaration) => evaluateDeclarationMotion(declaration, view)
1190
+ );
1191
+ reportMotionFindings(report, group.node, token, findings);
1192
+ }
1193
+ }
1194
+ for (const leaf of model.styleLeaves) {
1195
+ const findings = evaluateDeclarationMotion(parseStyleLeaf(leaf), view);
1196
+ reportMotionFindings(report, leaf.node, leaf.property, findings);
1197
+ }
1198
+ for (const key of model.motionKeys) {
1199
+ const findings = evaluateMotionKey(key.property);
1200
+ reportMotionFindings(report, key.node, key.property, findings);
1201
+ }
1202
+ }
1203
+ function createAnimatedPropertiesRule(view) {
1204
+ return {
1205
+ meta: {
1206
+ type: "problem",
1207
+ docs: { description: "Animate paint properties only." },
1208
+ messages: MOTION_MESSAGES,
1209
+ schema: []
1210
+ },
1211
+ create(context) {
1212
+ return {
1213
+ "Program:exit"() {
1214
+ const report = createUniqueReporter(context);
1215
+ const model = collectFrontendModel(context.sourceCode);
1216
+ reportModelMotion(report, model, view);
1217
+ }
1218
+ };
1219
+ }
1220
+ };
1221
+ }
1222
+ var DEFAULT_PALETTE_MESSAGE = "{{source}} uses {{variable}} from Tailwind's default palette; use a project color token.";
1223
+ var COLOR_FUNCTIONS = /* @__PURE__ */ new Set([
1224
+ "rgb",
1225
+ "rgba",
1226
+ "hsl",
1227
+ "hsla",
1228
+ "hwb",
1229
+ "lab",
1230
+ "lch",
1231
+ "oklab",
1232
+ "oklch",
1233
+ "color"
1234
+ ]);
1235
+ var KEYWORD_COLORS = /* @__PURE__ */ new Set(["transparent", "currentcolor"]);
1236
+ var THEME_COLOR = /^--color-/;
1237
+ var THEME_FUNCTIONS = /* @__PURE__ */ new Set(["var", "theme", "--theme"]);
1238
+ function isKeywordColor(node) {
1239
+ return node?.type === "Identifier" && KEYWORD_COLORS.has(node.name.toLowerCase());
1240
+ }
1241
+ function isNamedColor(node) {
1242
+ return node.type === "Identifier" && !isKeywordColor(node) && lexer.matchType("named-color", node).error === null;
1243
+ }
1244
+ function functionArguments(node) {
1245
+ return node.type === "Function" ? node.children.filter((child) => child.type !== "Operator") : [];
1246
+ }
1247
+ function hasTokenChannels(node) {
1248
+ const name = node.type === "Function" ? node.name.toLowerCase() : "";
1249
+ const [first, ...rest] = functionArguments(node);
1250
+ if (first?.type === "Identifier" && first.name.toLowerCase() === "from") {
1251
+ const [origin, ...channels2] = rest;
1252
+ const skipped = name === "color" ? channels2.slice(1) : channels2;
1253
+ return isVariableReference(origin) && skipped.every(
1254
+ (channel) => channel.type === "Identifier" || isVariableReference(channel)
1255
+ );
1256
+ }
1257
+ const channels = name === "color" && first?.type === "Identifier" ? rest : [first, ...rest];
1258
+ return channels.every((channel) => isVariableReference(channel));
1259
+ }
1260
+ function hasTokenOperands(node) {
1261
+ const [, ...operands] = splitOnCommas(
1262
+ node.type === "Function" ? node.children : []
1263
+ );
1264
+ return operands.every(
1265
+ (operand) => operand.every(
1266
+ (part) => part.type === "Percentage" || isVariableReference(part) || isKeywordColor(part)
1267
+ )
1268
+ );
1269
+ }
1270
+ function containsLiteral(node) {
1271
+ if (node.type === "Hash" || isNamedColor(node)) {
1272
+ return true;
1273
+ }
1274
+ if (node.type !== "Function" && node.type !== "Value" && node.type !== "Parentheses") {
1275
+ return false;
1276
+ }
1277
+ const name = node.type === "Function" ? node.name.toLowerCase() : "";
1278
+ if (name === "var") {
1279
+ return splitOnCommas(node.children).slice(1).some((fallback) => fallback.some((part) => containsLiteral(part)));
1280
+ }
1281
+ if (COLOR_FUNCTIONS.has(name)) {
1282
+ return !hasTokenChannels(node);
1283
+ }
1284
+ if (name === "color-mix") {
1285
+ return !hasTokenOperands(node);
1286
+ }
1287
+ return node.children.some((child) => containsLiteral(child));
1288
+ }
1289
+ function containsColorLiteral(text) {
1290
+ return containsLiteral(parseValue(text));
1291
+ }
1292
+ function substituteVariableFallbacks(node) {
1293
+ if (isVariableReference(node)) {
1294
+ const comma = node.children.findIndex(
1295
+ (child) => child.type === "Operator" && child.value === ","
1296
+ );
1297
+ return comma === -1 ? [node] : node.children.slice(comma + 1).flatMap((child) => substituteVariableFallbacks(child));
1298
+ }
1299
+ const children = listCssChildren(node);
1300
+ if (children.length === 0) {
1301
+ return [node];
1302
+ }
1303
+ const substituted = children.flatMap(
1304
+ (child) => substituteVariableFallbacks(child)
1305
+ );
1306
+ return [{ ...node, children: substituted }];
1307
+ }
1308
+ function canSetColor(declaration) {
1309
+ if (declaration.property.startsWith("--")) {
1310
+ return true;
1311
+ }
1312
+ const [value = declaration.value] = substituteVariableFallbacks(
1313
+ declaration.value
1314
+ );
1315
+ const match = lexer.matchProperty(declaration.property, value);
1316
+ if (match.error !== null) {
1317
+ return true;
1318
+ }
1319
+ return listValueNodes(value).some(
1320
+ (node) => match.isType(node, "color")
1321
+ );
1322
+ }
1323
+ function themeColorVariable(candidate) {
1324
+ return `--color-${candidate.value ?? ""}`;
1325
+ }
1326
+ function projectThemeValue(candidate, view) {
1327
+ const variable = themeColorVariable(candidate);
1328
+ return candidate.valueKind === "named" && !view.isDefaultThemeVariable(variable) ? view.themeValue(variable) : void 0;
1329
+ }
1330
+ function isProjectThemeColor(candidate, view) {
1331
+ return projectThemeValue(candidate, view) !== void 0;
1332
+ }
1333
+ function listAliasedPaletteColors(candidate, view) {
1334
+ const value = projectThemeValue(candidate, view);
1335
+ return value === void 0 ? [] : listDefaultPaletteColors(parseValue(value), view);
1336
+ }
1337
+ function listDefaultPaletteColors(value, view) {
1338
+ return listValueNodes(value).flatMap((node) => {
1339
+ const isThemeReference = node.type === "Function" && THEME_FUNCTIONS.has(node.name.toLowerCase());
1340
+ const name = isThemeReference ? firstIdentifierArgument(node) : void 0;
1341
+ const isDefaultColor = name !== void 0 && THEME_COLOR.test(name) && view.isDefaultThemeVariable(name);
1342
+ return isDefaultColor ? [name] : [];
1343
+ });
1344
+ }
1345
+
1346
+ // src/eslint-config/next/frontend/rules/color-tokens.ts
1347
+ function hasColorLiteral(token, declarations, view) {
1348
+ const inputs = view.candidatesOf(token).flatMap((candidate) => candidate.arbitraryInputs);
1349
+ return inputs.some((input) => containsColorLiteral(input)) && declarations.some((declaration) => canSetColor(declaration));
1350
+ }
1351
+ function collectExemptPaletteColors(token, view) {
1352
+ return new Set(
1353
+ view.candidatesOf(token).flatMap((candidate) => listAliasedPaletteColors(candidate, view))
1354
+ );
1355
+ }
1356
+ function checkClassGroup(report, group, view) {
1357
+ for (const { token, declarations } of compileClassGroup(group, view)) {
1358
+ if (hasColorLiteral(token, declarations, view)) {
1359
+ report({
1360
+ node: group.node,
1361
+ messageId: "literal",
1362
+ data: { source: token }
1363
+ });
1364
+ }
1365
+ const exemptVariables = collectExemptPaletteColors(token, view);
1366
+ for (const declaration of declarations) {
1367
+ const paletteColors = listDefaultPaletteColors(declaration.value, view);
1368
+ const variables = paletteColors.filter(
1369
+ (variable) => !exemptVariables.has(variable)
1370
+ );
1371
+ for (const variable of variables) {
1372
+ report({
1373
+ node: group.node,
1374
+ messageId: "defaultPalette",
1375
+ data: { source: token, variable }
1376
+ });
1377
+ }
1378
+ }
1379
+ }
1380
+ }
1381
+ function checkStyleLeaf(report, leaf, view) {
1382
+ const declaration = parseStyleLeaf(leaf);
1383
+ if (!declaration) {
1384
+ return;
1385
+ }
1386
+ const variables = listDefaultPaletteColors(declaration.value, view);
1387
+ for (const variable of variables) {
1388
+ report({
1389
+ node: leaf.node,
1390
+ messageId: "defaultPalette",
1391
+ data: { source: leaf.property, variable }
1392
+ });
1393
+ }
1394
+ }
1395
+ function createColorTokensRule(view) {
1396
+ return {
1397
+ meta: {
1398
+ type: "problem",
1399
+ docs: { description: "Use project color tokens." },
1400
+ messages: {
1401
+ literal: "{{source}} writes a color literal; use a project color token such as bg-(--token).",
1402
+ defaultPalette: DEFAULT_PALETTE_MESSAGE
1403
+ },
1404
+ schema: []
1405
+ },
1406
+ create(context) {
1407
+ return {
1408
+ "Program:exit"() {
1409
+ const report = createUniqueReporter(context);
1410
+ const model = collectFrontendModel(context.sourceCode);
1411
+ for (const group of model.classGroups) {
1412
+ checkClassGroup(report, group, view);
1413
+ }
1414
+ for (const leaf of model.styleLeaves) {
1415
+ checkStyleLeaf(report, leaf, view);
1416
+ }
1417
+ }
1418
+ };
1419
+ }
1420
+ };
1421
+ }
1422
+
1423
+ // src/eslint-config/next/frontend/rules/css-animated-properties.ts
1424
+ function createCssAnimatedPropertiesRule(view) {
1425
+ return {
1426
+ meta: {
1427
+ type: "problem",
1428
+ docs: { description: "Animate paint properties only." },
1429
+ messages: MOTION_MESSAGES,
1430
+ schema: []
1431
+ },
1432
+ create(context) {
1433
+ const report = (motionReport) => {
1434
+ context.report(motionReport);
1435
+ };
1436
+ return {
1437
+ StyleSheet(node) {
1438
+ for (const declaration of collectStylesheetDeclarations(node)) {
1439
+ const findings = [
1440
+ ...evaluateTransition(declaration),
1441
+ ...evaluateKeyframeDeclaration(declaration),
1442
+ ...evaluateAnimation(declaration, view)
1443
+ ];
1444
+ reportMotionFindings(
1445
+ report,
1446
+ declaration.node ?? node,
1447
+ declaration.property,
1448
+ findings
1449
+ );
1450
+ }
1451
+ }
1452
+ };
1453
+ }
1454
+ };
1455
+ }
1456
+
1457
+ // src/eslint-config/next/frontend/rules/css-color-tokens.ts
1458
+ function isThemeDefinition(declaration) {
1459
+ return declaration.inTheme && declaration.property.startsWith("--");
1460
+ }
1461
+ function createCssColorTokensRule(view) {
1462
+ return {
1463
+ meta: {
1464
+ type: "problem",
1465
+ docs: { description: "Use project color tokens." },
1466
+ messages: { defaultPalette: DEFAULT_PALETTE_MESSAGE },
1467
+ schema: []
1468
+ },
1469
+ create(context) {
1470
+ return {
1471
+ StyleSheet(node) {
1472
+ for (const declaration of collectStylesheetDeclarations(node)) {
1473
+ if (isThemeDefinition(declaration)) {
1474
+ continue;
1475
+ }
1476
+ const variables = new Set(
1477
+ listDefaultPaletteColors(declaration.value, view)
1478
+ );
1479
+ for (const variable of variables) {
1480
+ context.report({
1481
+ node: declaration.node ?? node,
1482
+ messageId: "defaultPalette",
1483
+ data: { source: declaration.property, variable }
1484
+ });
1485
+ }
1486
+ }
1487
+ }
1488
+ };
1489
+ }
1490
+ };
1491
+ }
1492
+ var FOCUS_SELECTOR = /(?<!\\):focus/;
1493
+ var SHADOW_VARIABLES = /* @__PURE__ */ new Set(["--tw-shadow", "--tw-inset-shadow"]);
1494
+ var OUTLINE_WIDTH_KEYWORDS = /* @__PURE__ */ new Set(["thin", "medium", "thick"]);
1495
+ var EARLY_LAYERS = /* @__PURE__ */ new Set(["theme", "base", "components"]);
1496
+ var RING_SHADOW_VARIABLES = ["--tw-ring-offset-shadow", "--tw-ring-shadow"];
1497
+ var UNPAIRED_OFFSET_LABEL = "a ring offset color only alongside focus-visible:ring-offset-<n> (required when above 0)";
1498
+ var ROLE_LIMITS = {
1499
+ "outline-hidden": {
1500
+ minimum: 1,
1501
+ maximum: 1,
1502
+ label: "focus-visible:outline-hidden"
1503
+ },
1504
+ "ring-width": {
1505
+ minimum: 1,
1506
+ maximum: 1,
1507
+ label: "focus-visible:ring or focus-visible:ring-<n>"
1508
+ },
1509
+ "ring-color": { minimum: 0, maximum: 1, label: "one ring color" },
1510
+ "offset-width": { minimum: 0, maximum: 1, label: "one ring offset" },
1511
+ "zero-offset-width": { minimum: 0, maximum: 1, label: "one ring offset" },
1512
+ "offset-color": { minimum: 0, maximum: 1, label: "one ring offset color" }
1513
+ };
1514
+ var RING_FAMILIES = [
1515
+ {
1516
+ utility: "ring-offset",
1517
+ widthVariable: "--tw-ring-offset-width",
1518
+ widthRole: offsetWidthRole,
1519
+ colorVariable: "--tw-ring-offset-color",
1520
+ colorRole: "offset-color"
1521
+ },
1522
+ {
1523
+ utility: "ring",
1524
+ widthVariable: "--tw-ring-shadow",
1525
+ widthRole: ringWidthRole,
1526
+ colorVariable: "--tw-ring-color",
1527
+ colorRole: "ring-color"
1528
+ }
1529
+ ];
1530
+ function isFocusSelector(selector) {
1531
+ return FOCUS_SELECTOR.test(selector);
1532
+ }
1533
+ function isFocusIndicatorProperty(property$1) {
1534
+ const name = property(property$1).basename;
1535
+ return name.startsWith("outline") || name.startsWith("--tw-ring-") || name.startsWith("--tw-inset-ring-") || name === "forced-color-adjust" || name === "all";
1536
+ }
1537
+ function isBoxShadowProperty(property$1) {
1538
+ return property(property$1).basename === "box-shadow";
1539
+ }
1540
+ function composesRing(declaration) {
1541
+ const variables = new Set(
1542
+ listValueNodes(declaration.value).flatMap((node) => {
1543
+ const name = referencedVariable(node);
1544
+ return name === void 0 ? [] : [name];
1545
+ })
1546
+ );
1547
+ return RING_SHADOW_VARIABLES.every((variable) => variables.has(variable));
1548
+ }
1549
+ function isShadowValue(declaration) {
1550
+ const text = declaration.text.trim().toLowerCase();
1551
+ if (text === "none" || CSS_WIDE_KEYWORDS.has(text)) {
1552
+ return false;
1553
+ }
1554
+ return text.includes("var(") || lexer.matchProperty("box-shadow", declaration.value).error === null;
1555
+ }
1556
+ function isShadowVariableReset(declaration) {
1557
+ return SHADOW_VARIABLES.has(declaration.property) && !isShadowValue(declaration);
1558
+ }
1559
+ function isScopedClassDeclaration(declaration) {
1560
+ return declaration.selectors.some((selector) => isFocusSelector(selector)) || isFocusIndicatorProperty(declaration.property) || declaration.property === "--tw-outline-style" || isBoxShadowProperty(declaration.property) && !composesRing(declaration) || isShadowVariableReset(declaration);
1561
+ }
1562
+ function isFocusScopedToken(token, view) {
1563
+ const declarations = view.declarationsOf(token);
1564
+ if (!declarations) {
1565
+ return false;
1566
+ }
1567
+ return declarations.some((declaration) => isScopedClassDeclaration(declaration)) || view.candidatesOf(token).some(
1568
+ (candidate) => candidate.arbitraryProperty && SHADOW_VARIABLES.has(candidate.utility)
1569
+ );
1570
+ }
1571
+ function plainVariableOfNode(node) {
1572
+ const name = node?.type === "Function" && node.children.length === 1 ? referencedVariable(node) : void 0;
1573
+ return name?.startsWith("--") ? name : void 0;
1574
+ }
1575
+ function plainVariableOfValue(value) {
1576
+ const [only, ...rest] = value?.children ?? [];
1577
+ return rest.length === 0 ? plainVariableOfNode(only) : void 0;
1578
+ }
1579
+ function isTokenColor(declarations, property, view) {
1580
+ const variable = plainVariableOfValue(
1581
+ declarations.find((declaration) => declaration.property === property)?.value
1582
+ );
1583
+ return variable !== void 0 && !view.isDefaultThemeVariable(variable);
1584
+ }
1585
+ function isWholeNumberAtLeast(value, minimum) {
1586
+ return value !== void 0 && /^\d+$/.test(value) && Number(value) >= minimum;
1587
+ }
1588
+ function ringWidthRole(candidate) {
1589
+ const isRingWidth = candidate.valueKind === "none" || candidate.valueKind === "named" && isWholeNumberAtLeast(candidate.value, 1);
1590
+ return isRingWidth ? "ring-width" : void 0;
1591
+ }
1592
+ function offsetWidthRole(candidate) {
1593
+ if (candidate.valueKind !== "named" || !isWholeNumberAtLeast(candidate.value, 0)) {
1594
+ return void 0;
1595
+ }
1596
+ return Number(candidate.value) === 0 ? "zero-offset-width" : "offset-width";
1597
+ }
1598
+ function ringRole(token, view) {
1599
+ const declarations = view.declarationsOf(token) ?? [];
1600
+ const setsVariable = (variable) => declarations.some((declaration) => declaration.property === variable);
1601
+ const family = RING_FAMILIES.find(
1602
+ (ringFamily) => setsVariable(ringFamily.widthVariable) || setsVariable(ringFamily.colorVariable)
1603
+ );
1604
+ const candidate = view.candidatesOf(token).find((parse2) => parse2.utility === family?.utility);
1605
+ if (!family || !candidate) {
1606
+ return void 0;
1607
+ }
1608
+ if (setsVariable(family.widthVariable)) {
1609
+ return family.widthRole(candidate);
1610
+ }
1611
+ const isColor = candidate.modifierKind === "none" && (isTokenColor(declarations, family.colorVariable, view) || isProjectThemeColor(candidate, view));
1612
+ return isColor ? family.colorRole : void 0;
1613
+ }
1614
+ function p1Role(token, view) {
1615
+ const candidates = view.candidatesOf(token);
1616
+ const wellFormed = candidates.every(
1617
+ (candidate) => !candidate.important && candidate.variants.length === 1 && candidate.variants[0] === "focus-visible"
1618
+ );
1619
+ if (!wellFormed || candidates.length === 0) {
1620
+ return void 0;
1621
+ }
1622
+ const outlineHidden = candidates.every(
1623
+ (candidate) => candidate.utility === "outline-hidden" && candidate.valueKind === "none" || candidate.utility === "outline" && candidate.valueKind === "named" && candidate.value === "hidden"
1624
+ );
1625
+ if (outlineHidden) {
1626
+ return "outline-hidden";
1627
+ }
1628
+ return ringRole(token, view);
1629
+ }
1630
+ function listOutOfRangeLabels(counts) {
1631
+ const offsetWidths = counts.get("offset-width") ?? 0;
1632
+ const zeroOffsetWidths = counts.get("zero-offset-width") ?? 0;
1633
+ const offsetColors = counts.get("offset-color") ?? 0;
1634
+ const labels = new Set(
1635
+ Object.entries(ROLE_LIMITS).filter(([role, limit]) => {
1636
+ const count = counts.get(role) ?? 0;
1637
+ return count < limit.minimum || count > limit.maximum;
1638
+ }).map(([, limit]) => limit.label)
1639
+ );
1640
+ if (offsetWidths + zeroOffsetWidths > 1) {
1641
+ labels.add(ROLE_LIMITS["offset-width"].label);
1642
+ }
1643
+ if (offsetColors < offsetWidths || offsetColors > offsetWidths + zeroOffsetWidths) {
1644
+ labels.add(UNPAIRED_OFFSET_LABEL);
1645
+ }
1646
+ return [...labels];
1647
+ }
1648
+ function evaluateFocusGroup(tokens, view) {
1649
+ const scoped = tokens.filter((token) => isFocusScopedToken(token, view));
1650
+ if (scoped.length === 0) {
1651
+ return { scoped, offending: [], outOfRange: [] };
1652
+ }
1653
+ const counts = /* @__PURE__ */ new Map();
1654
+ const offending = [];
1655
+ for (const token of scoped) {
1656
+ const role = p1Role(token, view);
1657
+ if (role === void 0) {
1658
+ offending.push(token);
1659
+ } else {
1660
+ counts.set(role, (counts.get(role) ?? 0) + 1);
1661
+ }
1662
+ }
1663
+ return { scoped, offending, outOfRange: listOutOfRangeLabels(counts) };
1664
+ }
1665
+ function isFocusScopedCssDeclaration(declaration) {
1666
+ if (isFocusIndicatorProperty(declaration.property) || isShadowVariableReset(declaration)) {
1667
+ return true;
1668
+ }
1669
+ if (!isBoxShadowProperty(declaration.property)) {
1670
+ return false;
1671
+ }
1672
+ if (declaration.important) {
1673
+ return true;
1674
+ }
1675
+ const firstLayer = declaration.layer?.split(".")[0];
1676
+ const beatsUtilities = firstLayer === void 0 || !EARLY_LAYERS.has(firstLayer) || declaration.inKeyframes;
1677
+ return beatsUtilities && !composesRing(declaration);
1678
+ }
1679
+ function endsWithFocusVisible(selector) {
1680
+ const last = listCssChildren(selector).at(-1);
1681
+ return last?.type === "PseudoClassSelector" && last.name === "focus-visible";
1682
+ }
1683
+ function isNonZeroWidth(width) {
1684
+ if (!width || lexer.matchType("line-width", width).error !== null) {
1685
+ return false;
1686
+ }
1687
+ if (width.type === "Dimension") {
1688
+ return Number(width.value) > 0;
1689
+ }
1690
+ return width.type === "Identifier" && OUTLINE_WIDTH_KEYWORDS.has(width.name);
1691
+ }
1692
+ function isC1Outline(value) {
1693
+ const [width, style, color, ...rest] = value.children;
1694
+ const solid = style?.type === "Identifier" && style.name === "solid";
1695
+ return rest.length === 0 && solid && isNonZeroWidth(width) && plainVariableOfNode(color) !== void 0;
1696
+ }
1697
+ function isC1Declarations(declarations) {
1698
+ const counts = /* @__PURE__ */ new Map();
1699
+ for (const { property } of declarations) {
1700
+ counts.set(property, (counts.get(property) ?? 0) + 1);
1701
+ }
1702
+ const outlines = declarations.filter(
1703
+ (declaration) => declaration.property === "outline"
1704
+ );
1705
+ const otherProperties = [...counts.keys()].filter(
1706
+ (property) => property !== "outline" && property !== "outline-offset"
1707
+ );
1708
+ return counts.get("outline") === 1 && (counts.get("outline-offset") ?? 0) <= 1 && otherProperties.length === 0 && declarations.every((declaration) => !declaration.important) && outlines.every((declaration) => isC1Outline(declaration.value));
1709
+ }
1710
+ function allSelectorsEndWithFocusVisible(block) {
1711
+ if (block.type === "Atrule") {
1712
+ return focusVariant(block) === "focus-visible";
1713
+ }
1714
+ return block.prelude.type === "SelectorList" && block.prelude.children.every((selector) => endsWithFocusVisible(selector));
1715
+ }
1716
+ function matchesC1(block, declarations) {
1717
+ return allSelectorsEndWithFocusVisible(block) && isC1Declarations(declarations);
1718
+ }
1719
+
1720
+ // src/eslint-config/next/frontend/rules/css-focus-styling.ts
1721
+ function groupFocusBlocks(declarations) {
1722
+ const groups = /* @__PURE__ */ new Map();
1723
+ for (const declaration of declarations) {
1724
+ const block = declaration.block;
1725
+ if (!block || !declaration.selectors.some((selector) => isFocusSelector(selector))) {
1726
+ continue;
1727
+ }
1728
+ const group = groups.get(block) ?? [];
1729
+ group.push(declaration);
1730
+ groups.set(block, group);
1731
+ }
1732
+ return groups;
1733
+ }
1734
+ var cssFocusStylingRule = {
1735
+ meta: {
1736
+ type: "problem",
1737
+ docs: { description: "Allow only the C1 focus pattern." },
1738
+ messages: {
1739
+ focusRule: "A focus rule must be exactly C1: every selector ends in :focus-visible (or the rule is an @variant focus-visible block), and the only declarations are outline: <non-zero width> solid var(--token) and an optional outline-offset, without !important.",
1740
+ focusDeclaration: "{{property}} outside a C1 focus rule can hide or override the focus indicator; move focus styling into a C1 rule, and put a decorative box-shadow in @layer components or use filter: drop-shadow()."
1741
+ },
1742
+ schema: []
1743
+ },
1744
+ create(context) {
1745
+ return {
1746
+ StyleSheet(node) {
1747
+ const declarations = collectStylesheetDeclarations(node);
1748
+ const focusBlocks = groupFocusBlocks(declarations);
1749
+ for (const [block, blockDeclarations] of focusBlocks) {
1750
+ if (!matchesC1(block, blockDeclarations)) {
1751
+ context.report({ node: block, messageId: "focusRule" });
1752
+ }
1753
+ }
1754
+ const grouped = new Set([...focusBlocks.values()].flat());
1755
+ for (const declaration of declarations) {
1756
+ if (declaration.node && !grouped.has(declaration) && isFocusScopedCssDeclaration(declaration)) {
1757
+ context.report({
1758
+ node: declaration.node,
1759
+ messageId: "focusDeclaration",
1760
+ data: { property: declaration.property }
1761
+ });
1762
+ }
1763
+ }
1764
+ }
1765
+ };
1766
+ }
1767
+ };
1768
+
1769
+ // src/eslint-config/next/frontend/rules/css-no-apply.ts
1770
+ var cssNoApplyRule = {
1771
+ meta: {
1772
+ type: "problem",
1773
+ docs: { description: "Disallow @apply." },
1774
+ messages: {
1775
+ apply: "Write the classes in markup or plain declarations here; @apply hides them from the lint rules."
1776
+ },
1777
+ schema: []
1778
+ },
1779
+ create(context) {
1780
+ return {
1781
+ Atrule(node) {
1782
+ if (node.name.toLowerCase() === "apply") {
1783
+ context.report({ node, messageId: "apply" });
1784
+ }
1785
+ }
1786
+ };
1787
+ }
1788
+ };
1789
+
1790
+ // src/eslint-config/next/frontend/rules/css-parsed-fully.ts
1791
+ var cssParsedFullyRule = {
1792
+ meta: {
1793
+ type: "problem",
1794
+ docs: { description: "Require blocks to parse fully." },
1795
+ messages: {
1796
+ unparsed: "This part of the block did not parse, so no rule checks it; rewrite it as plain declarations and nested rules, wrapping declarations that sit between nested rules in & { \u2026 } so they keep their order.",
1797
+ swallowed: "The declarations before this nested rule did not parse, so no rule checks them; wrap them in & { \u2026 } where they stand, or move @keyframes into an @theme block of their own."
1798
+ },
1799
+ schema: []
1800
+ },
1801
+ create(context) {
1802
+ return {
1803
+ Block(node) {
1804
+ for (const child of node.children) {
1805
+ if (child.type === "Raw") {
1806
+ context.report({ node: child, messageId: "unparsed" });
1807
+ }
1808
+ }
1809
+ },
1810
+ Rule(node) {
1811
+ if (node.prelude.type === "Raw" && /[:;]/.test(node.prelude.value)) {
1812
+ context.report({ node: node.prelude, messageId: "swallowed" });
1813
+ }
1814
+ }
1815
+ };
1816
+ }
1817
+ };
1818
+
1819
+ // src/eslint-config/next/frontend/concepts/viewport.ts
1820
+ var VH_UNIT_MESSAGE = "{{source}} uses vh, which ignores mobile browser chrome; use dvh, svh or lvh, such as h-dvh or min-h-svh.";
1821
+ function usesVhUnit(declaration) {
1822
+ return listValueNodes(declaration.value).some(
1823
+ (node) => node.type === "Dimension" && node.unit.toLowerCase() === "vh"
1824
+ );
1825
+ }
1826
+
1827
+ // src/eslint-config/next/frontend/css-declaration-rule.ts
1828
+ function createCssDeclarationRule(options) {
1829
+ return {
1830
+ meta: {
1831
+ type: "problem",
1832
+ docs: { description: options.description },
1833
+ messages: { [options.messageId]: options.message },
1834
+ schema: []
1835
+ },
1836
+ create(context) {
1837
+ return {
1838
+ StyleSheet(node) {
1839
+ for (const declaration of collectStylesheetDeclarations(node)) {
1840
+ if (options.matches(declaration)) {
1841
+ context.report({
1842
+ node: declaration.node ?? node,
1843
+ messageId: options.messageId,
1844
+ data: { source: options.describe(declaration) }
1845
+ });
1846
+ }
1847
+ }
1848
+ }
1849
+ };
1850
+ }
1851
+ };
1852
+ }
1853
+
1854
+ // src/eslint-config/next/frontend/rules/css-viewport-units.ts
1855
+ var cssViewportUnitsRule = createCssDeclarationRule({
1856
+ description: "Disallow vh units.",
1857
+ messageId: "vhUnit",
1858
+ message: VH_UNIT_MESSAGE,
1859
+ matches: usesVhUnit,
1860
+ describe: (declaration) => declaration.property
1861
+ });
1862
+
1863
+ // src/eslint-config/next/frontend/concepts/z-index.ts
1864
+ var Z_INDEX_CAP = 50;
1865
+ var Z_INDEX_MESSAGE = `z-index must be at most ${String(Z_INDEX_CAP)}, negative, auto, or a var() whose theme value or fallback is; {{source}} is not. To fix it, use the scale up to ${String(Z_INDEX_CAP)} or a named layer such as z-(--layer).`;
1866
+ var MAX_VARIABLE_DEPTH = 4;
1867
+ function calcArguments(node) {
1868
+ return node.type === "Function" && node.name.toLowerCase() === "calc" ? node.children : [];
1869
+ }
1870
+ function computeProduct(node) {
1871
+ const [left, operator, right, ...rest] = calcArguments(node);
1872
+ const isMultiplication = operator?.type === "Operator" && operator.value === "*";
1873
+ if (rest.length > 0 || !isMultiplication || left?.type !== "Number" || right?.type !== "Number") {
1874
+ return void 0;
1875
+ }
1876
+ return Number(left.value) * Number(right.value);
1877
+ }
1878
+ function resolveVariable(node, view) {
1879
+ const name = firstIdentifierArgument(node);
1880
+ const themeValue = name === void 0 ? void 0 : view.themeValue(name);
1881
+ if (themeValue !== void 0) {
1882
+ return parseValue(themeValue);
1883
+ }
1884
+ const fallback = listCssChildren(node).at(2);
1885
+ return fallback?.type === "Value" ? fallback : void 0;
1886
+ }
1887
+ function isAllowedValue(value, view, depth) {
1888
+ const [node, ...rest] = value.children;
1889
+ if (rest.length > 0 || !node) {
1890
+ return false;
1891
+ }
1892
+ if (node.type === "Number") {
1893
+ return Number(node.value) <= Z_INDEX_CAP;
1894
+ }
1895
+ if (node.type === "Identifier") {
1896
+ return node.name.toLowerCase() === "auto";
1897
+ }
1898
+ if (isVariableReference(node)) {
1899
+ const resolved = resolveVariable(node, view);
1900
+ return resolved === void 0 || depth > 0 && isAllowedValue(resolved, view, depth - 1);
1901
+ }
1902
+ const product = computeProduct(node);
1903
+ return product !== void 0 && product <= Z_INDEX_CAP;
1904
+ }
1905
+ function exceedsZIndexCap(declaration, view) {
1906
+ return declaration.property === "z-index" && !isAllowedValue(declaration.value, view, MAX_VARIABLE_DEPTH);
1907
+ }
1908
+
1909
+ // src/eslint-config/next/frontend/rules/css-z-index.ts
1910
+ function createCssZIndexRule(view) {
1911
+ return createCssDeclarationRule({
1912
+ description: "Cap z-index values.",
1913
+ messageId: "exceeds",
1914
+ message: Z_INDEX_MESSAGE,
1915
+ matches: (declaration) => exceedsZIndexCap(declaration, view),
1916
+ describe: (declaration) => declaration.text
1917
+ });
1918
+ }
1919
+
1920
+ // src/eslint-config/next/frontend/rules/focus-styling.ts
1921
+ var FOCUS_HANDLER_ATTRIBUTES = /* @__PURE__ */ new Set([
1922
+ "onFocus",
1923
+ "onBlur",
1924
+ "onFocusCapture",
1925
+ "onBlurCapture"
1926
+ ]);
1927
+ var P1_DESCRIPTION = "focus-visible:outline-hidden focus-visible:ring-2 (plus an optional project ring color, ring-offset-<n> and project offset color)";
1928
+ function describeVerdict(verdict) {
1929
+ const parts = [];
1930
+ if (verdict.offending.length > 0) {
1931
+ parts.push(`not P1: ${verdict.offending.join(" ")}`);
1932
+ }
1933
+ if (verdict.outOfRange.length > 0) {
1934
+ parts.push(`missing or repeated: ${verdict.outOfRange.join(", ")}`);
1935
+ }
1936
+ return parts.join("; ");
1937
+ }
1938
+ function isStylingFocusHandler(handler) {
1939
+ return FOCUS_HANDLER_ATTRIBUTES.has(handler.attributeName) && handler.styleAssignments.length > 0;
1940
+ }
1941
+ function listStyleKeys(model) {
1942
+ return [...model.styleLeaves, ...model.motionKeys];
1943
+ }
1944
+ function resetsShadowVariable(key) {
1945
+ const declaration = parseStyleLeaf(key);
1946
+ return declaration !== void 0 && isShadowVariableReset(declaration);
1947
+ }
1948
+ function listWhileFocusLabels(element) {
1949
+ const attribute = element && findJsxAttribute(element, "whileFocus");
1950
+ const value = attribute && jsxAttributeValue(attribute);
1951
+ return listBranches(value).flatMap(
1952
+ (branch) => branch.type === "ArrayExpression" ? childNodes(branch, "elements").flatMap((item) => listStaticTexts(item)) : listStaticTexts(branch)
1953
+ );
1954
+ }
1955
+ function isWhileFocusKey(key) {
1956
+ if (!("attributeName" in key) || key.property === "transition") {
1957
+ return false;
1958
+ }
1959
+ return key.attributeName === "whileFocus" || key.variantLabel !== void 0 && listWhileFocusLabels(key.element).includes(key.variantLabel);
1960
+ }
1961
+ function isIndicatorKey(key) {
1962
+ return isFocusIndicatorProperty(key.property) || resetsShadowVariable(key) || isWhileFocusKey(key);
1963
+ }
1964
+ function evaluateClassGroups(model, view) {
1965
+ return model.classGroups.map((group) => ({
1966
+ node: group.node,
1967
+ verdict: evaluateFocusGroup(group.tokens, view)
1968
+ }));
1969
+ }
1970
+ function selectFocusScopedNodes(groups) {
1971
+ return new Set(
1972
+ groups.filter((group) => group.verdict.scoped.length > 0).map((group) => group.node)
1973
+ );
1974
+ }
1975
+ function collectFocusStyledElements(model, scopedNodes) {
1976
+ const elements = /* @__PURE__ */ new Set();
1977
+ for (const group of model.classGroups) {
1978
+ if (group.element && scopedNodes.has(group.node)) {
1979
+ elements.add(group.element);
1980
+ }
1981
+ }
1982
+ for (const key of listStyleKeys(model)) {
1983
+ if (key.element && isIndicatorKey(key)) {
1984
+ elements.add(key.element);
1985
+ }
1986
+ }
1987
+ for (const handler of model.handlers) {
1988
+ if (isStylingFocusHandler(handler)) {
1989
+ elements.add(handler.element);
1990
+ }
1991
+ }
1992
+ return elements;
1993
+ }
1994
+ function isScopedHandler(handler, focusStyled) {
1995
+ const writesShadow = handler.styleAssignments.some(
1996
+ (assignment) => isBoxShadowProperty(assignment.property)
1997
+ );
1998
+ return isStylingFocusHandler(handler) || focusStyled.has(handler.element) && writesShadow;
1999
+ }
2000
+ function selectScopedHandlers(model, focusStyled) {
2001
+ const covered = /* @__PURE__ */ new Set();
2002
+ return model.handlers.filter((handler) => {
2003
+ if (!isScopedHandler(handler, focusStyled) || handler.styleAssignments.every((assignment) => covered.has(assignment))) {
2004
+ return false;
2005
+ }
2006
+ for (const assignment of handler.styleAssignments) {
2007
+ covered.add(assignment);
2008
+ }
2009
+ return true;
2010
+ });
2011
+ }
2012
+ function reportClassGroups(report, groups) {
2013
+ for (const { node, verdict } of groups) {
2014
+ if (verdict.offending.length > 0 || verdict.outOfRange.length > 0) {
2015
+ report({
2016
+ node,
2017
+ messageId: "classGroup",
2018
+ data: { problems: describeVerdict(verdict) }
2019
+ });
2020
+ }
2021
+ }
2022
+ }
2023
+ function reportStyleKeys(report, model, focusStyled) {
2024
+ for (const key of listStyleKeys(model)) {
2025
+ const isOnFocusStyledElement = key.element !== void 0 && focusStyled.has(key.element);
2026
+ if (isIndicatorKey(key) || isBoxShadowProperty(key.property) && isOnFocusStyledElement) {
2027
+ report({
2028
+ node: key.node,
2029
+ messageId: isWhileFocusKey(key) ? "whileFocus" : "inlineStyle",
2030
+ data: { property: key.property }
2031
+ });
2032
+ }
2033
+ }
2034
+ }
2035
+ function reportHandlers(report, handlers) {
2036
+ for (const handler of handlers) {
2037
+ report({
2038
+ node: handler.attribute,
2039
+ messageId: "handler",
2040
+ data: { attribute: handler.attributeName }
2041
+ });
2042
+ }
2043
+ }
2044
+ function reportAssignments(report, model, reportedHandlers) {
2045
+ const covered = new Set(
2046
+ reportedHandlers.flatMap((handler) => handler.styleAssignments)
2047
+ );
2048
+ for (const assignment of model.styleAssignments) {
2049
+ if (isFocusIndicatorProperty(assignment.property) && !covered.has(assignment)) {
2050
+ report({
2051
+ node: assignment.node,
2052
+ messageId: "assignment",
2053
+ data: { property: assignment.property }
2054
+ });
2055
+ }
2056
+ }
2057
+ }
2058
+ function createFocusStylingRule(view) {
2059
+ return {
2060
+ meta: {
2061
+ type: "problem",
2062
+ docs: { description: "Allow only the P1 focus pattern." },
2063
+ messages: {
2064
+ classGroup: `Focus styling in this class string must be exactly ${P1_DESCRIPTION}; {{problems}}.`,
2065
+ inlineStyle: `Inline {{property}} changes the focus indicator; use ${P1_DESCRIPTION}.`,
2066
+ whileFocus: `whileFocus styles the focused state with {{property}}; focus styling must be exactly ${P1_DESCRIPTION}.`,
2067
+ handler: `This {{attribute}} handler styles the focus indicator imperatively; use ${P1_DESCRIPTION}.`,
2068
+ assignment: `.style writes to {{property}} change the focus indicator; use ${P1_DESCRIPTION}.`
2069
+ },
2070
+ schema: []
2071
+ },
2072
+ create(context) {
2073
+ return {
2074
+ "Program:exit"() {
2075
+ const report = createUniqueReporter(context);
2076
+ const model = collectFrontendModel(context.sourceCode);
2077
+ const groups = evaluateClassGroups(model, view);
2078
+ const scopedNodes = selectFocusScopedNodes(groups);
2079
+ const focusStyled = collectFocusStyledElements(model, scopedNodes);
2080
+ const scopedHandlers = selectScopedHandlers(model, focusStyled);
2081
+ reportClassGroups(report, groups);
2082
+ reportStyleKeys(report, model, focusStyled);
2083
+ reportHandlers(report, scopedHandlers);
2084
+ reportAssignments(report, model, scopedHandlers);
2085
+ }
2086
+ };
2087
+ }
2088
+ };
2089
+ }
2090
+
2091
+ // src/eslint-config/next/frontend/rules/form-control-has-label.ts
2092
+ var CONTROLS = /* @__PURE__ */ new Set(["input", "textarea", "select"]);
2093
+ var LABELABLE_ELEMENTS = /* @__PURE__ */ new Set([
2094
+ "button",
2095
+ "input",
2096
+ "meter",
2097
+ "output",
2098
+ "progress",
2099
+ "select",
2100
+ "textarea"
2101
+ ]);
2102
+ var EXEMPT_INPUT_TYPES = /* @__PURE__ */ new Set([
2103
+ "hidden",
2104
+ "submit",
2105
+ "reset",
2106
+ "button",
2107
+ "image"
2108
+ ]);
2109
+ var LABEL_ATTRIBUTES = ["aria-label", "aria-labelledby"];
2110
+ var SOURCE_KEYED_TYPES = /* @__PURE__ */ new Set([
2111
+ "MemberExpression",
2112
+ "ChainExpression",
2113
+ "TemplateLiteral"
2114
+ ]);
2115
+ function attributeText(opening, name) {
2116
+ const attribute = findJsxAttribute(opening, name);
2117
+ return attribute ? staticText(jsxAttributeValue(attribute)) : void 0;
2118
+ }
2119
+ function isStaticallyAbsent(value) {
2120
+ return value === void 0 || value.type === "Literal" && value.value === null || value.type === "Identifier" && value.name === "undefined" || staticText(value) === "";
2121
+ }
2122
+ function hasAriaName(opening) {
2123
+ return LABEL_ATTRIBUTES.some((name) => {
2124
+ const attribute = findJsxAttribute(opening, name);
2125
+ return attribute !== void 0 && !isStaticallyAbsent(jsxAttributeValue(attribute));
2126
+ });
2127
+ }
2128
+ function isExemptControl(opening, name) {
2129
+ const typeValue = attributeText(opening, "type");
2130
+ const spread = childNodes(opening, "attributes").some(
2131
+ (attribute) => attribute.type === "JSXSpreadAttribute"
2132
+ );
2133
+ return spread || name === "input" && typeValue !== void 0 && EXEMPT_INPUT_TYPES.has(typeValue);
2134
+ }
2135
+ function isLabelable(opening) {
2136
+ const name = jsxElementName(opening) ?? "";
2137
+ return LABELABLE_ELEMENTS.has(name) && !(name === "input" && attributeText(opening, "type") === "hidden");
2138
+ }
2139
+ function declarationKey(variable) {
2140
+ const range = variable?.identifiers[0]?.range;
2141
+ return range ? `variable:${String(range[0])}` : void 0;
2142
+ }
2143
+ function variableKey(identifier, sourceCode) {
2144
+ const scope = sourceCode.getScope(toRuleNode(identifier));
2145
+ return declarationKey(findVariable(scope, String(identifier.name)));
2146
+ }
2147
+ function isValueReference(reference) {
2148
+ return !("isValueReference" in reference) || reference.isValueReference === true;
2149
+ }
2150
+ function listContainedReferences(scope, expression) {
2151
+ const ownReferences = scope.references.filter(
2152
+ (reference) => isValueReference(reference) && containsNode(expression, toAstNode(reference.identifier))
2153
+ );
2154
+ const nestedReferences = scope.childScopes.filter((child) => containsNode(expression, toAstNode(child.block))).flatMap((child) => listContainedReferences(child, expression));
2155
+ return [...ownReferences, ...nestedReferences];
2156
+ }
2157
+ function referenceKey(reference) {
2158
+ return declarationKey(reference.resolved) ?? `global:${reference.identifier.name}`;
2159
+ }
2160
+ function sourceKey(expression, sourceCode) {
2161
+ const node = toRuleNode(expression);
2162
+ const references = listContainedReferences(
2163
+ sourceCode.getScope(node),
2164
+ expression
2165
+ );
2166
+ const variables = references.map((reference) => referenceKey(reference));
2167
+ return `source:${sourceCode.getText(node)}@${variables.join(",")}`;
2168
+ }
2169
+ function idKey(attribute, sourceCode) {
2170
+ const value = attribute ? jsxAttributeValue(attribute) : void 0;
2171
+ const text = staticText(value);
2172
+ if (text !== void 0) {
2173
+ return text === "" ? void 0 : `string:${text}`;
2174
+ }
2175
+ if (value?.type === "Identifier") {
2176
+ return variableKey(value, sourceCode);
2177
+ }
2178
+ return value && SOURCE_KEYED_TYPES.has(value.type) ? sourceKey(value, sourceCode) : void 0;
2179
+ }
2180
+ function functionName(node) {
2181
+ const parent = node.parent;
2182
+ const ownName = childString(childNode(node, "id"), "name");
2183
+ if (node.type === "FunctionDeclaration") {
2184
+ return ownName;
2185
+ }
2186
+ if (parent?.type !== "VariableDeclarator" || parent.init !== node) {
2187
+ return void 0;
2188
+ }
2189
+ return ownName ?? childString(childNode(parent, "id"), "name");
2190
+ }
2191
+ function enclosingComponent(node) {
2192
+ let outermostFunction;
2193
+ let current = node;
2194
+ while (current.parent) {
2195
+ if (FUNCTION_TYPES.has(current.type)) {
2196
+ if (/^[A-Z]/.test(functionName(current) ?? "")) {
2197
+ return current;
2198
+ }
2199
+ outermostFunction = current;
2200
+ }
2201
+ current = current.parent;
2202
+ }
2203
+ return outermostFunction ?? current;
2204
+ }
2205
+ function isLabelElement(node) {
2206
+ const opening = childNode(node, "openingElement");
2207
+ return node.type === "JSXElement" && opening !== void 0 && jsxElementName(opening) === "label";
2208
+ }
2209
+ function enclosingLabel(opening) {
2210
+ let current = opening.parent?.parent;
2211
+ while (current && !isLabelElement(current)) {
2212
+ current = current.parent;
2213
+ }
2214
+ return current;
2215
+ }
2216
+ function areMutuallyExclusive(first, second) {
2217
+ let current = first.parent;
2218
+ while (current && !(current.type === "ConditionalExpression" && containsNode(current, second))) {
2219
+ current = current.parent;
2220
+ }
2221
+ const consequent = childNode(current, "consequent");
2222
+ const alternate = childNode(current, "alternate");
2223
+ if (!consequent || !alternate) {
2224
+ return false;
2225
+ }
2226
+ return containsNode(consequent, first) && containsNode(alternate, second) || containsNode(alternate, first) && containsNode(consequent, second);
2227
+ }
2228
+ function hasWrappingLabel(opening, controlKey, openings, sourceCode) {
2229
+ const label = enclosingLabel(opening);
2230
+ const labelOpening = childNode(label, "openingElement");
2231
+ if (!label || !labelOpening) {
2232
+ return false;
2233
+ }
2234
+ const htmlFor = findJsxAttribute(labelOpening, "htmlFor");
2235
+ if (htmlFor !== void 0) {
2236
+ return controlKey !== void 0 && idKey(htmlFor, sourceCode) === controlKey;
2237
+ }
2238
+ const firstLabelable = openings.find(
2239
+ (candidate) => containsNode(label, candidate) && isLabelable(candidate) && !areMutuallyExclusive(candidate, opening)
2240
+ );
2241
+ return firstLabelable === opening;
2242
+ }
2243
+ function buildLabelTarget(opening, sourceCode) {
2244
+ if (jsxElementName(opening) !== "label") {
2245
+ return void 0;
2246
+ }
2247
+ const key = idKey(findJsxAttribute(opening, "htmlFor"), sourceCode);
2248
+ return key === void 0 ? void 0 : { component: enclosingComponent(opening), key };
2249
+ }
2250
+ function needsLabel(opening) {
2251
+ const name = jsxElementName(opening) ?? "";
2252
+ return CONTROLS.has(name) && !isExemptControl(opening, name) && !hasAriaName(opening);
2253
+ }
2254
+ var formControlHasLabelRule = {
2255
+ meta: {
2256
+ type: "problem",
2257
+ docs: { description: "Require a label on form controls." },
2258
+ messages: {
2259
+ unlabelled: "Give this <{{element}}> a <label>, a matching <label htmlFor>, or an aria-label or aria-labelledby; a placeholder is not a label."
2260
+ },
2261
+ schema: []
2262
+ },
2263
+ create(context) {
2264
+ const sourceCode = context.sourceCode;
2265
+ const controls = [];
2266
+ const labels = [];
2267
+ const openings = [];
2268
+ return {
2269
+ JSXOpeningElement(node) {
2270
+ const opening = toAstNode(node);
2271
+ openings.push(opening);
2272
+ const label = buildLabelTarget(opening, sourceCode);
2273
+ if (label) {
2274
+ labels.push(label);
2275
+ }
2276
+ if (needsLabel(opening)) {
2277
+ controls.push(opening);
2278
+ }
2279
+ },
2280
+ "Program:exit"() {
2281
+ for (const control of controls) {
2282
+ const key = idKey(findJsxAttribute(control, "id"), sourceCode);
2283
+ const component = enclosingComponent(control);
2284
+ const sibling = key !== void 0 && labels.some(
2285
+ (label) => label.key === key && label.component === component
2286
+ );
2287
+ if (!sibling && !hasWrappingLabel(control, key, openings, sourceCode)) {
2288
+ context.report({
2289
+ node: toRuleNode(control),
2290
+ messageId: "unlabelled",
2291
+ data: { element: jsxElementName(control) ?? "" }
2292
+ });
2293
+ }
2294
+ }
2295
+ }
2296
+ };
2297
+ }
2298
+ };
2299
+
2300
+ // src/eslint-config/next/frontend/rules/no-embedded-style.ts
2301
+ var noEmbeddedStyleRule = {
2302
+ meta: {
2303
+ type: "problem",
2304
+ docs: { description: "Disallow JSX <style> elements." },
2305
+ messages: {
2306
+ embedded: "Move this CSS into a project stylesheet, where the CSS rules check it."
2307
+ },
2308
+ schema: []
2309
+ },
2310
+ create(context) {
2311
+ return {
2312
+ JSXOpeningElement(node) {
2313
+ if (jsxElementName(toAstNode(node)) === "style") {
2314
+ context.report({ node, messageId: "embedded" });
2315
+ }
2316
+ }
2317
+ };
2318
+ }
2319
+ };
2320
+
2321
+ // src/eslint-config/next/frontend/declaration-rule.ts
2322
+ function checkClassGroup2(report, group, view, options) {
2323
+ for (const { token, declarations } of compileClassGroup(group, view)) {
2324
+ if (declarations.some((declaration) => options.matches(declaration))) {
2325
+ report({
2326
+ node: group.node,
2327
+ messageId: options.messageId,
2328
+ data: { source: token }
2329
+ });
2330
+ }
2331
+ }
2332
+ }
2333
+ function checkStyleLeaf2(report, leaf, options) {
2334
+ const declaration = parseStyleLeaf(leaf);
2335
+ if (declaration && options.matches(declaration)) {
2336
+ report({
2337
+ node: leaf.node,
2338
+ messageId: options.messageId,
2339
+ data: { source: declaration.text }
2340
+ });
2341
+ }
2342
+ }
2343
+ function createDeclarationRule(view, options) {
2344
+ return {
2345
+ meta: {
2346
+ type: "problem",
2347
+ docs: { description: options.description },
2348
+ messages: { [options.messageId]: options.message },
2349
+ schema: []
2350
+ },
2351
+ create(context) {
2352
+ return {
2353
+ "Program:exit"() {
2354
+ const report = createUniqueReporter(context);
2355
+ const model = collectFrontendModel(context.sourceCode);
2356
+ for (const group of model.classGroups) {
2357
+ checkClassGroup2(report, group, view, options);
2358
+ }
2359
+ for (const leaf of model.styleLeaves) {
2360
+ checkStyleLeaf2(report, leaf, options);
2361
+ }
2362
+ }
2363
+ };
2364
+ }
2365
+ };
2366
+ }
2367
+
2368
+ // src/eslint-config/next/frontend/rules/viewport-units.ts
2369
+ function createViewportUnitsRule(view) {
2370
+ return createDeclarationRule(view, {
2371
+ description: "Disallow vh units.",
2372
+ messageId: "vhUnit",
2373
+ message: VH_UNIT_MESSAGE,
2374
+ matches: usesVhUnit
2375
+ });
2376
+ }
2377
+
2378
+ // src/eslint-config/next/frontend/rules/z-index.ts
2379
+ function createZIndexRule(view) {
2380
+ return createDeclarationRule(view, {
2381
+ description: "Cap z-index values.",
2382
+ messageId: "exceeds",
2383
+ message: Z_INDEX_MESSAGE,
2384
+ matches: (declaration) => exceedsZIndexCap(declaration, view)
2385
+ });
2386
+ }
2387
+
2388
+ // src/eslint-config/next/frontend/plugin.ts
2389
+ function createFrontendRules(view) {
2390
+ return {
2391
+ javascript: {
2392
+ "form-control-has-label": formControlHasLabelRule,
2393
+ "focus-styling": createFocusStylingRule(view),
2394
+ "animated-properties": createAnimatedPropertiesRule(view),
2395
+ "viewport-units": createViewportUnitsRule(view),
2396
+ "z-index": createZIndexRule(view),
2397
+ "color-tokens": createColorTokensRule(view),
2398
+ "no-embedded-style": noEmbeddedStyleRule
2399
+ },
2400
+ css: {
2401
+ "css-focus-styling": cssFocusStylingRule,
2402
+ "css-animated-properties": createCssAnimatedPropertiesRule(view),
2403
+ "css-viewport-units": cssViewportUnitsRule,
2404
+ "css-z-index": createCssZIndexRule(view),
2405
+ "css-color-tokens": createCssColorTokensRule(view),
2406
+ "css-no-apply": cssNoApplyRule,
2407
+ "css-parsed-fully": cssParsedFullyRule
2408
+ }
2409
+ };
2410
+ }
2411
+ function createFrontendPlugin(rules) {
2412
+ return {
2413
+ meta: { name: "@pragmatiks/lint/eslint-config/next" },
2414
+ rules: { ...rules.javascript, ...rules.css }
2415
+ };
2416
+ }
2417
+
2418
+ // src/eslint-config/next/index.ts
2419
+ var ACCESSIBILITY_PLUGIN = "jsx-a11y";
2420
+ var LABEL_RULE = "jsx-a11y/control-has-associated-label";
2421
+ var TYPED_FILES = ["**/*.{ts,tsx,mts}"];
2422
+ var THREE_DOTS_PATTERN = String.raw`/[A-Za-z]\.\.\./`;
2423
+ var ELLIPSIS_MESSAGE = "Write the ellipsis glyph \u2026 instead of three dots.";
2424
+ var ZOOM_MESSAGE = "Never disable zoom in the viewport export.";
2425
+ var FORWARD_REF_MESSAGE = "Pass ref as a prop; React 19 does not need forwardRef.";
2426
+ var URL_ATTRIBUTE = String.raw`JSXAttribute[name.name=/^(href|src|action)$/]`;
2427
+ var ELLIPSIS_EXEMPT_LITERAL = [
2428
+ `${URL_ATTRIBUTE} Literal`,
2429
+ "ImportDeclaration > Literal",
2430
+ "ExportNamedDeclaration > Literal",
2431
+ "ExportAllDeclaration > Literal",
2432
+ "ImportExpression > Literal"
2433
+ ].join(", ");
2434
+ var PRESET_RECOVERY = "To fix it, pass [...nextVitals, ...nextTs] from eslint-config-next";
2435
+ var ICON_FAMILY = "@phosphor-icons/react";
2436
+ var ICON_PACKAGES = [
2437
+ "lucide-react",
2438
+ "react-icons",
2439
+ "react-icons/*",
2440
+ "@heroicons/*",
2441
+ "@tabler/icons-react",
2442
+ "@radix-ui/react-icons",
2443
+ "react-feather",
2444
+ "@fortawesome/*",
2445
+ "@mui/icons-material",
2446
+ "@mui/icons-material/*"
2447
+ ];
2448
+ var RESTRICTED_SYNTAX = [
2449
+ {
2450
+ selector: `JSXText[value=${THREE_DOTS_PATTERN}]`,
2451
+ message: ELLIPSIS_MESSAGE
2452
+ },
2453
+ {
2454
+ selector: `Literal[value=${THREE_DOTS_PATTERN}]:not(${ELLIPSIS_EXEMPT_LITERAL})`,
2455
+ message: ELLIPSIS_MESSAGE
2456
+ },
2457
+ {
2458
+ selector: `TemplateElement[value.cooked=${THREE_DOTS_PATTERN}]:not(${URL_ATTRIBUTE} TemplateElement)`,
2459
+ message: ELLIPSIS_MESSAGE
2460
+ },
2461
+ {
2462
+ selector: `JSXAttribute[name.name="onPaste"] CallExpression[callee.property.name="preventDefault"]`,
2463
+ message: "Never block pasting into inputs."
2464
+ },
2465
+ {
2466
+ selector: `Property:matches([key.name="userScalable"], [key.value="userScalable"])[value.value=false]`,
2467
+ message: ZOOM_MESSAGE
2468
+ },
2469
+ {
2470
+ selector: `Property:matches([key.name="maximumScale"], [key.value="maximumScale"])[value.value=1]`,
2471
+ message: ZOOM_MESSAGE
2472
+ },
2473
+ {
2474
+ selector: `JSXOpeningElement[name.name="link"] JSXAttribute[name.name="href"] Literal[value=/fonts.(googleapis|gstatic).com/]`,
2475
+ message: "Load fonts through next/font, not a <link> to Google Fonts."
2476
+ },
2477
+ {
2478
+ selector: `JSXText[value=/lorem ipsum|John Doe/i]`,
2479
+ message: "Replace placeholder content with real copy."
2480
+ }
2481
+ ];
2482
+ var RESTRICTED_IMPORTS = {
2483
+ paths: [
2484
+ {
2485
+ name: "react",
2486
+ importNames: ["forwardRef"],
2487
+ message: FORWARD_REF_MESSAGE
2488
+ }
2489
+ ],
2490
+ patterns: [
2491
+ {
2492
+ group: ICON_PACKAGES,
2493
+ message: `Use ${ICON_FAMILY}, the project's one icon family.`
2494
+ }
2495
+ ]
2496
+ };
2497
+ var RESTRICTED_PROPERTIES = {
2498
+ object: "React",
2499
+ property: "forwardRef",
2500
+ message: FORWARD_REF_MESSAGE
2501
+ };
2502
+ function isIgnoreOnly(config) {
2503
+ return Object.keys(config).every(
2504
+ (key) => key === "ignores" || key === "name"
2505
+ );
2506
+ }
2507
+ function findAccessibilityConfig(nextConfigs) {
2508
+ const matches = nextConfigs.filter(
2509
+ (config) => config.plugins?.[ACCESSIBILITY_PLUGIN] !== void 0
2510
+ );
2511
+ const [only] = matches;
2512
+ if (matches.length !== 1 || !only?.files) {
2513
+ throw new Error(
2514
+ `@pragmatiks/lint: pragmatiksNextConfig expects exactly one Next config object that registers ${ACCESSIBILITY_PLUGIN} with files; found ${String(matches.length)}. ${PRESET_RECOVERY}.`
2515
+ );
2516
+ }
2517
+ return only;
2518
+ }
2519
+ function scopeNextConfigs(nextConfigs, files) {
2520
+ return nextConfigs.map(
2521
+ (config) => config.files || isIgnoreOnly(config) ? config : { ...config, files }
2522
+ );
2523
+ }
2524
+ function buildLabelOptions(labelOptions) {
2525
+ const [first, ...rest] = labelOptions;
2526
+ const options = first ?? {};
2527
+ return [
2528
+ {
2529
+ ...options,
2530
+ ignoreElements: [...options.ignoreElements ?? [], "select"]
2531
+ },
2532
+ ...rest
2533
+ ];
2534
+ }
2535
+ function buildAccessibilityRules(accessibilityConfig) {
2536
+ const plugin = accessibilityConfig.plugins?.[ACCESSIBILITY_PLUGIN];
2537
+ const recommended = plugin?.flatConfigs?.recommended?.rules;
2538
+ const labelEntry = recommended?.[LABEL_RULE];
2539
+ if (!recommended || !Array.isArray(labelEntry)) {
2540
+ throw new Error(
2541
+ `@pragmatiks/lint: ${ACCESSIBILITY_PLUGIN} has no flat recommended preset with ${LABEL_RULE} options. ${PRESET_RECOVERY}.`
2542
+ );
2543
+ }
2544
+ const [, ...labelOptions] = labelEntry;
2545
+ return {
2546
+ ...recommended,
2547
+ [LABEL_RULE]: ["error", ...buildLabelOptions(labelOptions)],
2548
+ "jsx-a11y/no-autofocus": ["error", { ignoreNonDOM: true }]
2549
+ };
2550
+ }
2551
+ function buildRuleEntries(ruleIds) {
2552
+ return Object.fromEntries(
2553
+ ruleIds.map((ruleId) => [`pragmatiks/${ruleId}`, "error"])
2554
+ );
2555
+ }
2556
+ function buildFrontendConfig(accessibilityConfig, plugin, rules) {
2557
+ return {
2558
+ name: "pragmatiks/next/frontend",
2559
+ files: accessibilityConfig.files,
2560
+ plugins: { pragmatiks: plugin },
2561
+ rules: {
2562
+ ...buildAccessibilityRules(accessibilityConfig),
2563
+ ...buildRuleEntries(Object.keys(rules.javascript)),
2564
+ "no-restricted-syntax": ["error", ...RESTRICTED_SYNTAX],
2565
+ "no-restricted-imports": ["error", RESTRICTED_IMPORTS],
2566
+ "no-restricted-properties": ["error", RESTRICTED_PROPERTIES],
2567
+ "no-alert": "error",
2568
+ "unicorn/filename-case": ["error", FILENAME_CASE_OPTIONS],
2569
+ "unicorn/no-useless-undefined": ["error", NO_USELESS_UNDEFINED_OPTIONS]
2570
+ }
2571
+ };
2572
+ }
2573
+ function buildTypedConfig(rootDirectory) {
2574
+ return {
2575
+ name: "pragmatiks/next/typed",
2576
+ files: TYPED_FILES,
2577
+ plugins: { "react-x": reactX },
2578
+ languageOptions: {
2579
+ parserOptions: { projectService: true, tsconfigRootDir: rootDirectory }
2580
+ },
2581
+ rules: { "react-x/no-leaked-conditional-rendering": "error" }
2582
+ };
2583
+ }
2584
+ function buildCssConfig(plugin, rules) {
2585
+ return {
2586
+ name: "pragmatiks/next/css",
2587
+ files: ["**/*.css"],
2588
+ plugins: { css, pragmatiks: plugin },
2589
+ language: "css/css",
2590
+ languageOptions: { tolerant: true },
2591
+ rules: buildRuleEntries(Object.keys(rules.css))
2592
+ };
2593
+ }
2594
+ async function pragmatiksNextConfig(nextConfigs, options) {
2595
+ const accessibilityConfig = findAccessibilityConfig(nextConfigs);
2596
+ const view = await loadDesignSystem(options);
2597
+ const rules = createFrontendRules(view);
2598
+ const plugin = createFrontendPlugin(rules);
2599
+ return [
2600
+ ...scopeNextConfigs(nextConfigs, accessibilityConfig.files),
2601
+ ...pragmatiksConfig(),
2602
+ buildFrontendConfig(accessibilityConfig, plugin, rules),
2603
+ buildTypedConfig(options.rootDir),
2604
+ buildCssConfig(plugin, rules)
2605
+ ];
2606
+ }
2607
+
2608
+ export { pragmatiksNextConfig };
2609
+ //# sourceMappingURL=index.js.map
2610
+ //# sourceMappingURL=index.js.map