@cimplify/cli 0.13.3 → 0.13.4

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.
Files changed (96) hide show
  1. package/README.md +11 -8
  2. package/dist/{add-F7GKDXAD.mjs → add-542NLKYG.mjs} +4 -5
  3. package/dist/{assets-Y4JE6MSG.mjs → assets-OEY6FHHZ.mjs} +4 -5
  4. package/dist/{auth-step-up-EHL5PM2L.mjs → auth-step-up-ALEUQUCE.mjs} +5 -6
  5. package/dist/{cancel-QSZE2XC2.mjs → cancel-FD66LTTS.mjs} +4 -5
  6. package/dist/chunk-4O4G2CDE.mjs +162 -0
  7. package/dist/{chunk-C4M3DXKC.mjs → chunk-5KYR6WJB.mjs} +10 -2
  8. package/dist/{chunk-4YIFBYBV.mjs → chunk-5ZQURRIM.mjs} +1 -2
  9. package/dist/{chunk-QA36VSLX.mjs → chunk-6CHBU7O4.mjs} +1 -1
  10. package/dist/{chunk-QKDCMUWP.mjs → chunk-7NUSJVN3.mjs} +3 -2
  11. package/dist/{chunk-72VM62IN.mjs → chunk-HJQ6PF5N.mjs} +6 -6
  12. package/dist/{chunk-UP7N53C4.mjs → chunk-IK6NQHTG.mjs} +1 -1
  13. package/dist/{chunk-OVHZV57L.mjs → chunk-MLVBE5V2.mjs} +1 -1
  14. package/dist/{chunk-RR4NOZGY.mjs → chunk-OE7YRDLH.mjs} +5 -6
  15. package/dist/{chunk-VJJDGHZC.mjs → chunk-PDGKBATF.mjs} +1 -2
  16. package/dist/{chunk-5U5OXOPN.mjs → chunk-RU53TQLZ.mjs} +1 -1
  17. package/dist/{chunk-JPXK5BLA.mjs → chunk-TOQGLBWF.mjs} +1 -1
  18. package/dist/{chunk-SVJPU4K7.mjs → chunk-VCDGYXFU.mjs} +51 -43
  19. package/dist/{deploy-UTAYLZOM.mjs → deploy-SLRFPGTH.mjs} +8 -9
  20. package/dist/{dev-SYFBDJSY.mjs → dev-L3W6UXYN.mjs} +19 -26
  21. package/dist/dispatcher.mjs +41 -33
  22. package/dist/{doctor-QPCWLW4L.mjs → doctor-E6JH4VSN.mjs} +8 -9
  23. package/dist/{domains-OS3DXSDN.mjs → domains-7D7WJAMC.mjs} +5 -6
  24. package/dist/{env-UVO4OIEO.mjs → env-PAENNABF.mjs} +6 -7
  25. package/dist/{explain-XB67PYQQ.mjs → explain-SZPLL5MP.mjs} +3 -4
  26. package/dist/{inspect-ULWQHT7J.mjs → inspect-MGVG4Z44.mjs} +3 -4
  27. package/dist/introspect-SDHD3PVX.mjs +8 -0
  28. package/dist/{link-VQN2WPZO.mjs → link-KGNQWCSZ.mjs} +4 -5
  29. package/dist/lint-NM3APP7L.mjs +1327 -0
  30. package/dist/{list-DZTPWNU3.mjs → list-W5JABY7B.mjs} +3 -4
  31. package/dist/{login-UZ5Y7GIO.mjs → login-NCO4P5G5.mjs} +5 -6
  32. package/dist/{logout-XYWZR6QJ.mjs → logout-MF6SOLPE.mjs} +2 -3
  33. package/dist/{logs-YNXDXAF7.mjs → logs-76TCJBIQ.mjs} +4 -5
  34. package/dist/{mock-HFVAAYK2.mjs → mock-CAWNXSGC.mjs} +2 -3
  35. package/dist/{projects-RJ44DJKZ.mjs → projects-U75DOOR7.mjs} +4 -5
  36. package/dist/repo-IE5IOLR5.mjs +8 -0
  37. package/dist/{rollback-Z2PRL7FS.mjs → rollback-ZL3V7SYH.mjs} +5 -6
  38. package/dist/{status-RR3GGTVU.mjs → status-52HG63FE.mjs} +4 -5
  39. package/dist/{unlink-JBVZYOA3.mjs → unlink-TCG3NBAR.mjs} +2 -3
  40. package/dist/{update-B5HANRBK.mjs → update-447FR363.mjs} +4 -5
  41. package/dist/{whoami-5G32NNEP.mjs → whoami-B5Y5KSOZ.mjs} +3 -4
  42. package/package.json +3 -2
  43. package/templates/storefront-auto/app/categories/[slug]/page.tsx +1 -0
  44. package/templates/storefront-auto/app/collections/[slug]/page.tsx +1 -0
  45. package/templates/storefront-auto/app/page.tsx +4 -1
  46. package/templates/storefront-auto/app/products/[slug]/page.tsx +1 -0
  47. package/templates/storefront-auto/components/auth-error-toast.tsx +2 -2
  48. package/templates/storefront-auto/package.json +1 -1
  49. package/templates/storefront-bakery/app/categories/[slug]/page.tsx +1 -0
  50. package/templates/storefront-bakery/app/collections/[slug]/page.tsx +1 -0
  51. package/templates/storefront-bakery/app/page.tsx +1 -1
  52. package/templates/storefront-bakery/components/auth-error-toast.tsx +2 -2
  53. package/templates/storefront-bakery/package.json +1 -1
  54. package/templates/storefront-fashion/app/categories/[slug]/page.tsx +1 -0
  55. package/templates/storefront-fashion/app/collections/[slug]/page.tsx +1 -0
  56. package/templates/storefront-fashion/app/page.tsx +1 -1
  57. package/templates/storefront-fashion/app/products/[slug]/page.tsx +1 -0
  58. package/templates/storefront-fashion/components/auth-error-toast.tsx +2 -2
  59. package/templates/storefront-fashion/package.json +1 -1
  60. package/templates/storefront-grocery/app/categories/[slug]/page.tsx +1 -0
  61. package/templates/storefront-grocery/app/collections/[slug]/page.tsx +1 -0
  62. package/templates/storefront-grocery/app/page.tsx +1 -1
  63. package/templates/storefront-grocery/components/auth-error-toast.tsx +2 -2
  64. package/templates/storefront-grocery/package.json +1 -1
  65. package/templates/storefront-pharmacy/app/categories/[slug]/page.tsx +1 -0
  66. package/templates/storefront-pharmacy/app/collections/[slug]/page.tsx +1 -0
  67. package/templates/storefront-pharmacy/app/globals.css +11 -3
  68. package/templates/storefront-pharmacy/app/page.tsx +4 -1
  69. package/templates/storefront-pharmacy/app/products/[slug]/page.tsx +1 -0
  70. package/templates/storefront-pharmacy/components/auth-error-toast.tsx +2 -2
  71. package/templates/storefront-pharmacy/components/pharmacy-hero.tsx +10 -3
  72. package/templates/storefront-pharmacy/components/urgent-ctas.tsx +30 -5
  73. package/templates/storefront-pharmacy/package.json +1 -1
  74. package/templates/storefront-restaurant/app/categories/[slug]/page.tsx +1 -0
  75. package/templates/storefront-restaurant/app/collections/[slug]/page.tsx +1 -0
  76. package/templates/storefront-restaurant/app/page.tsx +1 -1
  77. package/templates/storefront-restaurant/components/auth-error-toast.tsx +2 -2
  78. package/templates/storefront-restaurant/package.json +1 -1
  79. package/templates/storefront-retail/app/categories/[slug]/page.tsx +1 -0
  80. package/templates/storefront-retail/app/collections/[slug]/page.tsx +1 -0
  81. package/templates/storefront-retail/app/page.tsx +1 -1
  82. package/templates/storefront-retail/app/products/[slug]/page.tsx +1 -0
  83. package/templates/storefront-retail/components/auth-error-toast.tsx +2 -2
  84. package/templates/storefront-retail/package.json +1 -1
  85. package/templates/storefront-services/app/book/book-client.tsx +3 -5
  86. package/templates/storefront-services/app/categories/[slug]/page.tsx +1 -0
  87. package/templates/storefront-services/app/collections/[slug]/page.tsx +1 -0
  88. package/templates/storefront-services/app/page.tsx +1 -1
  89. package/templates/storefront-services/components/auth-error-toast.tsx +2 -2
  90. package/templates/storefront-services/package.json +1 -1
  91. package/dist/agent-CEYZNYXA.mjs +0 -25
  92. package/dist/chunk-KNTFZ6LD.mjs +0 -2090
  93. package/dist/chunk-YPC4VU65.mjs +0 -76
  94. package/dist/chunk-ZPVN6UXL.mjs +0 -74
  95. package/dist/introspect-D4473WPV.mjs +0 -9
  96. package/dist/repo-OK6CVG3G.mjs +0 -9
@@ -0,0 +1,1327 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs, flagBool, flagString } from './chunk-5KYR6WJB.mjs';
3
+ import { isJsonMode, result, info, CliError, CLI_ERROR_CODE, configureOutput, OUTPUT_MODE } from './chunk-4O4G2CDE.mjs';
4
+ import { promises } from 'fs';
5
+ import path from 'path';
6
+ import { parse } from '@babel/parser';
7
+ import _traverse from '@babel/traverse';
8
+ import * as t from '@babel/types';
9
+ import { RE2JS } from 're2js';
10
+ import { starterLintRuleSet, MAX_LINT_RULE_MANIFEST_BYTES, validateLintRuleSet } from '@cimplify/sdk/lint';
11
+
12
+ // src/lint/format.ts
13
+ function plural(value, singular) {
14
+ return `${value} ${value === 1 ? singular : `${singular}s`}`;
15
+ }
16
+ function formatLintHuman(report) {
17
+ if (report.diagnostics.length === 0 && !report.truncated) {
18
+ const fixed = report.summary.fixed > 0 ? `; fixed ${plural(report.summary.fixed, "finding")}` : "";
19
+ return [`\u2713 Cimplify lint passed (${plural(report.summary.files, "file")}${fixed}).`];
20
+ }
21
+ const lines = [];
22
+ let currentFile = null;
23
+ for (const item of report.diagnostics) {
24
+ if (item.file !== currentFile) {
25
+ if (currentFile !== null) lines.push("");
26
+ currentFile = item.file;
27
+ lines.push(item.file);
28
+ }
29
+ const location = `${item.line}:${item.column}`;
30
+ const fixable = item.fixable ? " [fixable]" : "";
31
+ lines.push(
32
+ ` ${location} ${item.severity.padEnd(7)} ${item.message} ${item.ruleId}${fixable}`
33
+ );
34
+ }
35
+ if (report.truncated) {
36
+ if (lines.length > 0) lines.push("");
37
+ lines.push(
38
+ "! Diagnostic output was truncated at the safe analysis bound; more findings may remain."
39
+ );
40
+ }
41
+ lines.push("");
42
+ const summary = [
43
+ plural(report.summary.errors, "error"),
44
+ plural(report.summary.warnings, "warning")
45
+ ];
46
+ if (report.summary.fixed > 0) summary.push(`${plural(report.summary.fixed, "finding")} fixed`);
47
+ lines.push(`\u2717 ${summary.join(", ")} in ${plural(report.summary.files, "file")}.`);
48
+ return lines;
49
+ }
50
+ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"]);
51
+ var MAX_LINT_FILES = 5e3;
52
+ var MAX_TOTAL_SOURCE_BYTES = 64 * 1024 * 1024;
53
+ var DEFAULT_LINT_IGNORE_DIRECTORIES = /* @__PURE__ */ new Set([
54
+ ".cache",
55
+ ".cimplify",
56
+ ".git",
57
+ ".next",
58
+ ".output",
59
+ ".turbo",
60
+ "build",
61
+ "coverage",
62
+ "dist",
63
+ "node_modules",
64
+ "out"
65
+ ]);
66
+ function isIgnoredPath(cwd, absolutePath) {
67
+ const relative = path.relative(cwd, absolutePath);
68
+ if (relative.startsWith("..") || path.isAbsolute(relative)) return false;
69
+ return relative.split(path.sep).some((segment) => DEFAULT_LINT_IGNORE_DIRECTORIES.has(segment));
70
+ }
71
+ function isSourceFile(filePath) {
72
+ return SOURCE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
73
+ }
74
+ function isWithinDirectory(directory, candidate) {
75
+ const relative = path.relative(directory, candidate);
76
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
77
+ }
78
+ function pathEscapeError(cwd, candidate) {
79
+ return new CliError(
80
+ CLI_ERROR_CODE.INVALID_INPUT,
81
+ `Lint path escapes the project: ${path.relative(cwd, candidate) || candidate}`,
82
+ { exitCode: 2 }
83
+ );
84
+ }
85
+ function addSourceFile(filePath, size, output, budget) {
86
+ if (output.has(filePath)) return;
87
+ if (budget.files >= MAX_LINT_FILES) {
88
+ throw new CliError(
89
+ CLI_ERROR_CODE.INVALID_INPUT,
90
+ `Lint input exceeds ${MAX_LINT_FILES} source files; pass narrower paths.`,
91
+ { exitCode: 2 }
92
+ );
93
+ }
94
+ if (budget.bytes + size > MAX_TOTAL_SOURCE_BYTES) {
95
+ throw new CliError(
96
+ CLI_ERROR_CODE.INVALID_INPUT,
97
+ `Lint input exceeds ${MAX_TOTAL_SOURCE_BYTES} source bytes; pass narrower paths.`,
98
+ { exitCode: 2 }
99
+ );
100
+ }
101
+ output.add(filePath);
102
+ budget.files += 1;
103
+ budget.bytes += size;
104
+ }
105
+ async function collectDirectory(directory, physicalProjectRoot, output, budget) {
106
+ const physicalDirectory = await promises.realpath(directory);
107
+ if (!isWithinDirectory(physicalProjectRoot, physicalDirectory)) {
108
+ throw pathEscapeError(physicalProjectRoot, physicalDirectory);
109
+ }
110
+ const entries = await promises.readdir(directory, { withFileTypes: true });
111
+ entries.sort((left, right) => left.name.localeCompare(right.name));
112
+ for (const entry of entries) {
113
+ if (DEFAULT_LINT_IGNORE_DIRECTORIES.has(entry.name)) continue;
114
+ const absolute = path.join(directory, entry.name);
115
+ if (entry.isDirectory()) {
116
+ const stat = await promises.lstat(absolute);
117
+ if (!stat.isSymbolicLink()) {
118
+ await collectDirectory(absolute, physicalProjectRoot, output, budget);
119
+ }
120
+ } else if (entry.isFile() && isSourceFile(absolute)) {
121
+ const stat = await promises.lstat(absolute);
122
+ addSourceFile(absolute, stat.size, output, budget);
123
+ }
124
+ }
125
+ }
126
+ async function discoverLintFiles(cwd, inputPaths) {
127
+ const projectRoot = path.resolve(cwd);
128
+ const physicalProjectRoot = await promises.realpath(projectRoot);
129
+ const roots = inputPaths.length === 0 ? [projectRoot] : inputPaths.map((input) => path.resolve(projectRoot, input));
130
+ const files = /* @__PURE__ */ new Set();
131
+ const budget = { files: 0, bytes: 0 };
132
+ for (const root of roots) {
133
+ if (!isWithinDirectory(projectRoot, root)) throw pathEscapeError(projectRoot, root);
134
+ let stat;
135
+ try {
136
+ stat = await promises.lstat(root);
137
+ } catch (error) {
138
+ if (error.code === "ENOENT") {
139
+ throw new CliError(
140
+ CLI_ERROR_CODE.INVALID_INPUT,
141
+ `Lint path does not exist: ${path.relative(cwd, root) || "."}`,
142
+ {
143
+ exitCode: 2
144
+ }
145
+ );
146
+ }
147
+ throw error;
148
+ }
149
+ if (stat.isSymbolicLink()) continue;
150
+ if (isIgnoredPath(projectRoot, root)) continue;
151
+ const physicalRoot = await promises.realpath(root);
152
+ if (!isWithinDirectory(physicalProjectRoot, physicalRoot)) {
153
+ throw pathEscapeError(projectRoot, root);
154
+ }
155
+ if (stat.isDirectory()) {
156
+ await collectDirectory(root, physicalProjectRoot, files, budget);
157
+ } else if (stat.isFile() && isSourceFile(root)) {
158
+ addSourceFile(root, stat.size, files, budget);
159
+ }
160
+ }
161
+ return [...files].sort((left, right) => left.localeCompare(right));
162
+ }
163
+ function displayPath(cwd, absolutePath) {
164
+ const relative = path.relative(cwd, absolutePath);
165
+ return (relative || path.basename(absolutePath)).split(path.sep).join("/");
166
+ }
167
+
168
+ // src/lint/engine.ts
169
+ var traverse = _traverse.default ?? _traverse;
170
+ var MAX_ANALYSIS_WORKERS = 8;
171
+ var MAX_SOURCE_FILE_BYTES = 2 * 1024 * 1024;
172
+ var MAX_FILE_DIAGNOSTICS = 100;
173
+ var MAX_RUN_DIAGNOSTICS = 1e3;
174
+ var MAX_DIAGNOSTIC_MESSAGE_CHARS = 1e3;
175
+ var MAX_REPORTED_CLASS_TOKENS = 5;
176
+ var MAX_CLASS_FIX_TOKENS = 64;
177
+ var PARSE_ERROR_RULE_ID = "cimplify/parse-error";
178
+ var PARSE_ERROR_HELP = "Fix the syntax error before running Cimplify lint again.";
179
+ var FILE_TOO_LARGE_RULE_ID = "cimplify/file-too-large";
180
+ var FILE_TOO_LARGE_HELP = "Split or exclude this file before running Cimplify lint again.";
181
+ var PRICE_FIELD_PATTERN = /(?:^|_)price(?:$|_)/i;
182
+ function compileRules(rules) {
183
+ return rules.map((rule) => {
184
+ if (rule.primitive.kind === "required-wrapper-call") {
185
+ return {
186
+ rule,
187
+ subjectPattern: RE2JS.compile(rule.primitive.subjectNamePattern, RE2JS.CASE_INSENSITIVE)
188
+ };
189
+ }
190
+ if (rule.primitive.kind === "banned-classname-pattern") {
191
+ return {
192
+ rule,
193
+ classNamePatterns: rule.primitive.patterns.map((pattern) => RE2JS.compile(pattern))
194
+ };
195
+ }
196
+ return { rule };
197
+ });
198
+ }
199
+ function position(line, column, offset) {
200
+ return { line, column, offset };
201
+ }
202
+ function nodeRange(node) {
203
+ const start = node.start ?? 0;
204
+ const end = node.end ?? start;
205
+ const startLoc = node.loc?.start;
206
+ const endLoc = node.loc?.end;
207
+ return {
208
+ start: position(startLoc?.line ?? 1, (startLoc?.column ?? 0) + 1, start),
209
+ end: position(
210
+ endLoc?.line ?? startLoc?.line ?? 1,
211
+ (endLoc?.column ?? startLoc?.column ?? 0) + 1,
212
+ end
213
+ )
214
+ };
215
+ }
216
+ function diagnostic(file, rule, node, options = {}) {
217
+ const range = nodeRange(node);
218
+ const rawMessage = options.message ?? rule.message;
219
+ return {
220
+ file,
221
+ ruleId: rule.id,
222
+ severity: rule.severity,
223
+ message: rawMessage.length <= MAX_DIAGNOSTIC_MESSAGE_CHARS ? rawMessage : `${rawMessage.slice(0, MAX_DIAGNOSTIC_MESSAGE_CHARS - 1)}\u2026`,
224
+ help: rule.help,
225
+ line: range.start.line,
226
+ column: range.start.column,
227
+ range,
228
+ fixable: options.fix !== void 0,
229
+ ...options.fix ? { fix: options.fix } : {}
230
+ };
231
+ }
232
+ function parseErrorDiagnostic(file, error) {
233
+ const candidate = error;
234
+ const line = typeof candidate.loc?.line === "number" ? candidate.loc.line : 1;
235
+ const zeroColumn = typeof candidate.loc?.column === "number" ? candidate.loc.column : 0;
236
+ const offset = typeof candidate.loc?.index === "number" ? candidate.loc.index : typeof candidate.pos === "number" ? candidate.pos : 0;
237
+ const raw = typeof candidate.message === "string" ? candidate.message : String(error);
238
+ const message = raw.replace(/\s*\(\d+:\d+\)\s*$/, "");
239
+ return {
240
+ file,
241
+ ruleId: PARSE_ERROR_RULE_ID,
242
+ severity: "error",
243
+ message,
244
+ help: PARSE_ERROR_HELP,
245
+ line,
246
+ column: zeroColumn + 1,
247
+ range: {
248
+ start: position(line, zeroColumn + 1, offset),
249
+ end: position(line, zeroColumn + 1, offset)
250
+ },
251
+ fixable: false
252
+ };
253
+ }
254
+ function fileTooLargeDiagnostic(file) {
255
+ const start = position(1, 1, 0);
256
+ return {
257
+ file,
258
+ ruleId: FILE_TOO_LARGE_RULE_ID,
259
+ severity: "error",
260
+ message: `Split this file so it is no larger than ${MAX_SOURCE_FILE_BYTES} bytes.`,
261
+ help: FILE_TOO_LARGE_HELP,
262
+ line: 1,
263
+ column: 1,
264
+ range: { start, end: start },
265
+ fixable: false
266
+ };
267
+ }
268
+ function importedBindings(program) {
269
+ const bindings = [];
270
+ for (const statement of program.body) {
271
+ if (!t.isImportDeclaration(statement)) continue;
272
+ if (statement.importKind === "type") continue;
273
+ const source = statement.source.value;
274
+ for (const specifier of statement.specifiers) {
275
+ if (t.isImportSpecifier(specifier)) {
276
+ if (specifier.importKind === "type" || specifier.importKind === "typeof") continue;
277
+ const imported = t.isIdentifier(specifier.imported) ? specifier.imported.name : specifier.imported.value;
278
+ bindings.push({ source, imported, local: specifier.local.name });
279
+ } else if (t.isImportDefaultSpecifier(specifier)) {
280
+ bindings.push({ source, imported: "default", local: specifier.local.name });
281
+ } else {
282
+ bindings.push({ source, imported: "*", local: specifier.local.name });
283
+ }
284
+ }
285
+ }
286
+ return bindings;
287
+ }
288
+ function importedSources(program) {
289
+ return program.body.filter(t.isImportDeclaration).map((statement) => statement.source.value);
290
+ }
291
+ function sourcePatternMatches(source, pattern) {
292
+ return pattern.match === "exact" ? source === pattern.value : source === pattern.value || source.startsWith(`${pattern.value}/`);
293
+ }
294
+ function hasActivatingImport(sources, patterns) {
295
+ return sources.some(
296
+ (source) => patterns.some((pattern) => sourcePatternMatches(source, pattern))
297
+ );
298
+ }
299
+ function bindingMatches(localName, patterns, imports) {
300
+ return imports.some(
301
+ (binding) => binding.local === localName && patterns.some(
302
+ (pattern) => pattern.source === binding.source && pattern.imported.includes(binding.imported)
303
+ )
304
+ );
305
+ }
306
+ function unwrapExpression(node) {
307
+ let current = node ?? null;
308
+ while (current && (t.isTSAsExpression(current) || t.isTSSatisfiesExpression(current) || t.isTSNonNullExpression(current) || t.isParenthesizedExpression(current))) {
309
+ current = current.expression;
310
+ }
311
+ return current;
312
+ }
313
+ function propertyName(node) {
314
+ if (!node.computed && t.isIdentifier(node.property)) return node.property.name;
315
+ if (t.isStringLiteral(node.property)) return node.property.value;
316
+ return null;
317
+ }
318
+ function objectPropertyName(node) {
319
+ if (!node.computed && t.isIdentifier(node.key)) return node.key.name;
320
+ if (t.isStringLiteral(node.key)) return node.key.value;
321
+ return null;
322
+ }
323
+ function rootExpression(node) {
324
+ let current = unwrapExpression(node) ?? node;
325
+ while (t.isMemberExpression(current) || t.isOptionalMemberExpression(current)) {
326
+ current = unwrapExpression(current.object) ?? current.object;
327
+ }
328
+ return current;
329
+ }
330
+ function isFactoryCall(node, primitive, imports) {
331
+ const value = unwrapExpression(node);
332
+ return t.isCallExpression(value) && t.isIdentifier(value.callee) && bindingMatches(value.callee.name, primitive.factories, imports);
333
+ }
334
+ function isSdkResultInitializer(node, path4, primitive, imports) {
335
+ let value = unwrapExpression(node);
336
+ if (t.isAwaitExpression(value)) value = unwrapExpression(value.argument);
337
+ if (!t.isCallExpression(value)) return false;
338
+ const root = rootExpression(value.callee);
339
+ if (isFactoryCall(root, primitive, imports)) return true;
340
+ if (!t.isIdentifier(root)) return false;
341
+ const binding = path4.scope.getBinding(root.name);
342
+ if (!binding?.path.isVariableDeclarator()) return false;
343
+ return isFactoryCall(binding.path.node.init, primitive, imports);
344
+ }
345
+ function memberProperty(node, property) {
346
+ return (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) && propertyName(node) === property;
347
+ }
348
+ var NO_GUARD_IMPLICATIONS = { whenTrue: null, whenFalse: null };
349
+ function combineFacts(left, right) {
350
+ if (left === null) return right;
351
+ if (right === null || left === right) return left;
352
+ return null;
353
+ }
354
+ function commonFact(left, right) {
355
+ return left !== null && left === right ? left : null;
356
+ }
357
+ function negateImplications(implications) {
358
+ return { whenTrue: implications.whenFalse, whenFalse: implications.whenTrue };
359
+ }
360
+ function resultGuardImplications(node, name, guardProperty) {
361
+ const value = unwrapExpression(node);
362
+ if (!value) return NO_GUARD_IMPLICATIONS;
363
+ if ((t.isMemberExpression(value) || t.isOptionalMemberExpression(value)) && memberProperty(value, guardProperty) && t.isIdentifier(unwrapExpression(value.object), { name })) {
364
+ return { whenTrue: "success", whenFalse: "failure" };
365
+ }
366
+ if (t.isUnaryExpression(value, { operator: "!" })) {
367
+ return negateImplications(resultGuardImplications(value.argument, name, guardProperty));
368
+ }
369
+ if (t.isBinaryExpression(value) && ["==", "===", "!=", "!=="].includes(value.operator)) {
370
+ const leftBoolean = t.isBooleanLiteral(value.left) ? value.left.value : null;
371
+ const rightBoolean = t.isBooleanLiteral(value.right) ? value.right.value : null;
372
+ const guard = leftBoolean !== null ? resultGuardImplications(value.right, name, guardProperty) : rightBoolean !== null ? resultGuardImplications(value.left, name, guardProperty) : NO_GUARD_IMPLICATIONS;
373
+ const expected = leftBoolean ?? rightBoolean;
374
+ if (expected === null) return NO_GUARD_IMPLICATIONS;
375
+ const equality = value.operator === "==" || value.operator === "===";
376
+ return equality === expected ? guard : negateImplications(guard);
377
+ }
378
+ if (t.isLogicalExpression(value)) {
379
+ const left = resultGuardImplications(value.left, name, guardProperty);
380
+ const right = resultGuardImplications(value.right, name, guardProperty);
381
+ if (value.operator === "&&") {
382
+ return {
383
+ whenTrue: combineFacts(left.whenTrue, right.whenTrue),
384
+ whenFalse: commonFact(left.whenFalse, combineFacts(left.whenTrue, right.whenFalse))
385
+ };
386
+ }
387
+ if (value.operator === "||") {
388
+ return {
389
+ whenTrue: commonFact(left.whenTrue, combineFacts(left.whenFalse, right.whenTrue)),
390
+ whenFalse: combineFacts(left.whenFalse, right.whenFalse)
391
+ };
392
+ }
393
+ }
394
+ return NO_GUARD_IMPLICATIONS;
395
+ }
396
+ function guardImplicationsAtPath(path4, binding, name, guardProperty) {
397
+ if (path4.scope.getBinding(name) !== binding) return NO_GUARD_IMPLICATIONS;
398
+ return resultGuardImplications(path4.node, name, guardProperty);
399
+ }
400
+ function statementAlwaysTerminates(node) {
401
+ if (!node) return false;
402
+ if (t.isReturnStatement(node) || t.isThrowStatement(node) || t.isBreakStatement(node) || t.isContinueStatement(node)) {
403
+ return true;
404
+ }
405
+ if (t.isBlockStatement(node)) return statementAlwaysTerminates(node.body.at(-1));
406
+ if (t.isIfStatement(node)) {
407
+ return statementAlwaysTerminates(node.consequent) && statementAlwaysTerminates(node.alternate);
408
+ }
409
+ return false;
410
+ }
411
+ function accessIsInsideSuccessGuard(path4, binding, name, guardProperty) {
412
+ let child = path4;
413
+ for (let parent = path4.parentPath; parent; child = parent, parent = parent.parentPath) {
414
+ if (parent.isIfStatement() || parent.isConditionalExpression()) {
415
+ const implications = guardImplicationsAtPath(
416
+ parent.get("test"),
417
+ binding,
418
+ name,
419
+ guardProperty
420
+ );
421
+ if (child.node === parent.node.consequent && implications.whenTrue === "success" || child.node === parent.node.alternate && implications.whenFalse === "success") {
422
+ return true;
423
+ }
424
+ } else if (parent.isLogicalExpression() && child.node === parent.node.right) {
425
+ const implications = guardImplicationsAtPath(
426
+ parent.get("left"),
427
+ binding,
428
+ name,
429
+ guardProperty
430
+ );
431
+ if (parent.node.operator === "&&" && implications.whenTrue === "success" || parent.node.operator === "||" && implications.whenFalse === "success") {
432
+ return true;
433
+ }
434
+ }
435
+ }
436
+ return false;
437
+ }
438
+ function accessFollowsTerminatingFailureGuard(path4, binding, name, guardProperty) {
439
+ let statement = path4.getStatementParent();
440
+ while (statement) {
441
+ const container = statement.parentPath;
442
+ if (!container?.isBlockStatement() && !container?.isProgram()) return false;
443
+ const siblings = container.get("body");
444
+ const index = siblings.findIndex((candidate) => candidate.node === statement?.node);
445
+ for (let candidateIndex = index - 1; candidateIndex >= 0; candidateIndex--) {
446
+ const candidate = siblings[candidateIndex];
447
+ if (!candidate.isIfStatement()) continue;
448
+ const implications = guardImplicationsAtPath(
449
+ candidate.get("test"),
450
+ binding,
451
+ name,
452
+ guardProperty
453
+ );
454
+ if (implications.whenFalse === "success" && statementAlwaysTerminates(candidate.node.consequent) || implications.whenTrue === "success" && statementAlwaysTerminates(candidate.node.alternate)) {
455
+ return true;
456
+ }
457
+ }
458
+ if (container.parentPath?.isFunction()) return false;
459
+ statement = container.getStatementParent();
460
+ }
461
+ return false;
462
+ }
463
+ function bindingIsNarrowedForValueAccess(path4, binding, name, guardProperty) {
464
+ if (!binding.constant) return false;
465
+ return accessIsInsideSuccessGuard(path4, binding, name, guardProperty) || accessFollowsTerminatingFailureGuard(path4, binding, name, guardProperty);
466
+ }
467
+ function subjectName(node) {
468
+ const value = unwrapExpression(node);
469
+ if (!value) return null;
470
+ if (t.isIdentifier(value)) return value.name;
471
+ if (t.isMemberExpression(value) || t.isOptionalMemberExpression(value)) {
472
+ return propertyName(value);
473
+ }
474
+ return null;
475
+ }
476
+ function sourceMatches(source, primitive) {
477
+ return primitive.sources.some((pattern) => sourcePatternMatches(source, pattern));
478
+ }
479
+ function normalizedPropertyName(property, caseInsensitive) {
480
+ const name = objectPropertyName(property);
481
+ return name && caseInsensitive ? name.toLowerCase() : name;
482
+ }
483
+ function collectObjectPathMatches(node, primitive) {
484
+ const root = unwrapExpression(node);
485
+ if (!t.isObjectExpression(root)) return [];
486
+ const found = [];
487
+ function visit(container, path4, index) {
488
+ const expected = primitive.caseInsensitive ? path4[index].toLowerCase() : path4[index];
489
+ for (const property of container.properties) {
490
+ if (!t.isObjectProperty(property) && !t.isObjectMethod(property)) continue;
491
+ if (normalizedPropertyName(property, primitive.caseInsensitive) !== expected) continue;
492
+ if (index === path4.length - 1) {
493
+ found.push({ property, container });
494
+ continue;
495
+ }
496
+ if (!t.isObjectProperty(property)) continue;
497
+ const nested = unwrapExpression(property.value);
498
+ if (t.isObjectExpression(nested)) visit(nested, path4, index + 1);
499
+ }
500
+ }
501
+ for (const objectPath of primitive.objectPaths) {
502
+ if (objectPath.length > 0) visit(root, objectPath, 0);
503
+ }
504
+ return found;
505
+ }
506
+ function callMatchesPrimitive(node, path4, primitive) {
507
+ const callee = unwrapExpression(node.callee);
508
+ if (!t.isIdentifier(callee) || !primitive.callees.includes(callee.name)) return false;
509
+ return !primitive.globalCalleesOnly || path4.scope.getBinding(callee.name) === void 0;
510
+ }
511
+ function removeObjectPropertyFix(source, match) {
512
+ const { container, property } = match;
513
+ const propertyIndex = container.properties.indexOf(property);
514
+ const propertyStart = property.start;
515
+ const propertyEnd = property.end;
516
+ if (propertyIndex === -1 || propertyStart == null || propertyEnd == null) return void 0;
517
+ const previous = container.properties[propertyIndex - 1];
518
+ const next = container.properties[propertyIndex + 1];
519
+ let start = propertyStart;
520
+ let end = propertyEnd;
521
+ if (next?.start != null) {
522
+ end = next.start;
523
+ } else if (previous?.end != null) {
524
+ start = previous.end;
525
+ } else {
526
+ const containerStart = container.start;
527
+ const containerEnd = container.end;
528
+ if (containerStart == null || containerEnd == null || source[containerStart] !== "{" || source[containerEnd - 1] !== "}") {
529
+ return void 0;
530
+ }
531
+ start = containerStart + 1;
532
+ end = containerEnd - 1;
533
+ }
534
+ if (start < 0 || end <= start || end > source.length) return void 0;
535
+ return { edits: [{ start, end, text: "" }] };
536
+ }
537
+ function jsxName(node) {
538
+ if (t.isJSXIdentifier(node)) return node.name;
539
+ if (t.isJSXMemberExpression(node)) {
540
+ const object = jsxName(node.object);
541
+ return object ? `${object}.${node.property.name}` : node.property.name;
542
+ }
543
+ return null;
544
+ }
545
+ function priceImportEdit(ast, source, wrapper, imports, path4) {
546
+ const existing = imports.find(
547
+ (binding) => binding.source === wrapper.source && binding.imported === wrapper.imported && /^[A-Z]/.test(binding.local)
548
+ );
549
+ if (existing) return { localName: existing.local };
550
+ const programScope = path4.scope.getProgramParent();
551
+ if (programScope.hasBinding(wrapper.imported) || programScope.hasGlobal(wrapper.imported))
552
+ return null;
553
+ const importsInProgram = ast.program.body.filter(t.isImportDeclaration);
554
+ const lastImport = importsInProgram.at(-1);
555
+ const lastDirective = ast.program.directives.at(-1);
556
+ const interpreterEnd = ast.program.interpreter?.end ?? 0;
557
+ const insertAt = lastImport?.end ?? lastDirective?.end ?? interpreterEnd;
558
+ const statement = `import { ${wrapper.imported} } from ${JSON.stringify(wrapper.source)};`;
559
+ const prefix = insertAt > 0 && source[insertAt - 1] !== "\n" ? "\n" : "";
560
+ const suffix = source[insertAt] === "\n" ? "" : "\n";
561
+ return {
562
+ localName: wrapper.imported,
563
+ edit: { start: insertAt, end: insertAt, text: `${prefix}${statement}${suffix}` }
564
+ };
565
+ }
566
+ function jsxWrapperFix(ast, source, container, expression, primitive, imports, path4) {
567
+ if (primitive.wrapper.syntax !== "jsx") return void 0;
568
+ const unwrapped = unwrapExpression(expression);
569
+ if (!t.isMemberExpression(unwrapped) || !PRICE_FIELD_PATTERN.test(propertyName(unwrapped) ?? "")) {
570
+ return void 0;
571
+ }
572
+ const start = container.start;
573
+ const end = container.end;
574
+ const expressionStart = expression.start;
575
+ const expressionEnd = expression.end;
576
+ if (start == null || end == null || expressionStart == null || expressionEnd == null) {
577
+ return void 0;
578
+ }
579
+ const importResult = priceImportEdit(ast, source, primitive.wrapper, imports, path4);
580
+ if (!importResult) return void 0;
581
+ const edits = [
582
+ {
583
+ start,
584
+ end,
585
+ text: `<${importResult.localName} ${primitive.wrapper.prop}={${source.slice(expressionStart, expressionEnd)}} />`
586
+ }
587
+ ];
588
+ if (importResult.edit) edits.push(importResult.edit);
589
+ return { edits };
590
+ }
591
+ function collectClassNameExpression(node, output) {
592
+ const value = unwrapExpression(node);
593
+ if (!value) return;
594
+ if (t.isStringLiteral(value)) {
595
+ output.push({ node: value, value: value.value, fixNode: value });
596
+ return;
597
+ }
598
+ if (t.isTemplateLiteral(value)) {
599
+ if (value.expressions.length === 0) {
600
+ output.push({
601
+ node: value,
602
+ value: value.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw).join(""),
603
+ fixNode: value
604
+ });
605
+ return;
606
+ }
607
+ for (const quasi of value.quasis) {
608
+ output.push({ node: quasi, value: quasi.value.cooked ?? quasi.value.raw });
609
+ }
610
+ for (const expression of value.expressions) collectClassNameExpression(expression, output);
611
+ return;
612
+ }
613
+ if (t.isConditionalExpression(value)) {
614
+ collectClassNameExpression(value.consequent, output);
615
+ collectClassNameExpression(value.alternate, output);
616
+ return;
617
+ }
618
+ if (t.isLogicalExpression(value) || t.isBinaryExpression(value)) {
619
+ collectClassNameExpression(value.left, output);
620
+ collectClassNameExpression(value.right, output);
621
+ return;
622
+ }
623
+ if (t.isCallExpression(value) || t.isNewExpression(value)) {
624
+ for (const argument of value.arguments) {
625
+ if (!t.isSpreadElement(argument) && !t.isJSXNamespacedName(argument)) {
626
+ collectClassNameExpression(argument, output);
627
+ }
628
+ }
629
+ return;
630
+ }
631
+ if (t.isArrayExpression(value)) {
632
+ for (const element of value.elements) {
633
+ if (element && !t.isSpreadElement(element)) collectClassNameExpression(element, output);
634
+ }
635
+ return;
636
+ }
637
+ if (t.isObjectExpression(value)) {
638
+ for (const property of value.properties) {
639
+ if (!t.isObjectProperty(property) && !t.isObjectMethod(property)) continue;
640
+ const name = objectPropertyName(property);
641
+ if (name) output.push({ node: property.key, value: name });
642
+ }
643
+ }
644
+ }
645
+ function classNameFragments(attribute) {
646
+ if (t.isStringLiteral(attribute.value)) {
647
+ return [{ node: attribute.value, value: attribute.value.value, fixNode: attribute.value }];
648
+ }
649
+ if (!t.isJSXExpressionContainer(attribute.value)) return [];
650
+ const output = [];
651
+ collectClassNameExpression(attribute.value.expression, output);
652
+ return output;
653
+ }
654
+ function lastTopLevelSeparator(value, separator) {
655
+ let bracketDepth = 0;
656
+ let escaped = false;
657
+ let found = -1;
658
+ for (let index = 0; index < value.length; index++) {
659
+ const character = value[index];
660
+ if (escaped) {
661
+ escaped = false;
662
+ continue;
663
+ }
664
+ if (character === "\\") {
665
+ escaped = true;
666
+ continue;
667
+ }
668
+ if (character === "[") bracketDepth += 1;
669
+ else if (character === "]") bracketDepth = Math.max(0, bracketDepth - 1);
670
+ else if (character === separator && bracketDepth === 0) found = index;
671
+ }
672
+ return found;
673
+ }
674
+ function replaceClassToken(token, primitive) {
675
+ const lastColon = lastTopLevelSeparator(token, ":");
676
+ const prefix = lastColon === -1 ? "" : token.slice(0, lastColon + 1);
677
+ let terminal = token.slice(lastColon + 1);
678
+ const leadingImportant = terminal.startsWith("!") ? "!" : "";
679
+ const trailingImportant = terminal.endsWith("!") ? "!" : "";
680
+ if (leadingImportant) terminal = terminal.slice(1);
681
+ if (trailingImportant) terminal = terminal.slice(0, -1);
682
+ const opacityIndex = lastTopLevelSeparator(terminal, "/");
683
+ const opacity = opacityIndex === -1 ? "" : terminal.slice(opacityIndex);
684
+ const base = opacityIndex === -1 ? terminal : terminal.slice(0, opacityIndex);
685
+ const replacement = primitive.replacements?.find((candidate) => candidate.from === base);
686
+ return replacement ? `${prefix}${leadingImportant}${replacement.to}${opacity}${trailingImportant}` : null;
687
+ }
688
+ function terminalClassToken(token) {
689
+ const lastColon = lastTopLevelSeparator(token, ":");
690
+ let terminal = token.slice(lastColon + 1);
691
+ if (terminal.startsWith("!")) terminal = terminal.slice(1);
692
+ if (terminal.endsWith("!")) terminal = terminal.slice(0, -1);
693
+ const slash = lastTopLevelSeparator(terminal, "/");
694
+ const opacity = slash === -1 ? "" : terminal.slice(slash + 1);
695
+ if (opacity.length > 0 && [...opacity].every((character) => character >= "0" && character <= "9")) {
696
+ terminal = terminal.slice(0, slash);
697
+ }
698
+ return terminal;
699
+ }
700
+ function classNameFix(source, node, value, bannedTokens, primitive) {
701
+ const tokens = value.split(/(\s+)/);
702
+ let changed = false;
703
+ for (let index = 0; index < tokens.length; index++) {
704
+ const token = tokens[index];
705
+ if (!bannedTokens.has(token)) continue;
706
+ const replacement = replaceClassToken(token, primitive);
707
+ if (!replacement) return void 0;
708
+ tokens[index] = replacement;
709
+ changed = true;
710
+ }
711
+ if (!changed || node.start == null || node.end == null) return void 0;
712
+ const nextValue = tokens.join("");
713
+ const text = t.isTemplateLiteral(node) ? `\`${nextValue.replaceAll("\\", "\\\\").replaceAll("`", "\\`").replaceAll("${", "\\${")}\`` : JSON.stringify(nextValue);
714
+ if (source.slice(node.start, node.end) === text) return void 0;
715
+ return { edits: [{ start: node.start, end: node.end, text }] };
716
+ }
717
+ function analyzeSource(source, file, compiledRules, maxDiagnostics) {
718
+ if (maxDiagnostics <= 0) return { diagnostics: [], truncated: true };
719
+ let ast;
720
+ try {
721
+ ast = parse(source, {
722
+ sourceType: "unambiguous",
723
+ plugins: ["typescript", "jsx", "decorators-legacy"]
724
+ });
725
+ } catch (error) {
726
+ return { diagnostics: [parseErrorDiagnostic(file, error)], truncated: false };
727
+ }
728
+ const diagnostics = [];
729
+ let truncated = false;
730
+ const imports = importedBindings(ast.program);
731
+ const importSources = importedSources(ast.program);
732
+ class DiagnosticLimitReached extends Error {
733
+ }
734
+ function emit(item) {
735
+ if (diagnostics.length >= maxDiagnostics) {
736
+ truncated = true;
737
+ throw new DiagnosticLimitReached();
738
+ }
739
+ diagnostics.push(item);
740
+ }
741
+ try {
742
+ traverse(ast, {
743
+ ImportDeclaration(path4) {
744
+ for (const compiled of compiledRules) {
745
+ const primitive = compiled.rule.primitive;
746
+ if (primitive.kind === "banned-import" && sourceMatches(path4.node.source.value, primitive)) {
747
+ emit(diagnostic(file, compiled.rule, path4.node.source));
748
+ }
749
+ }
750
+ },
751
+ ExportNamedDeclaration(path4) {
752
+ if (!path4.node.source) return;
753
+ for (const compiled of compiledRules) {
754
+ const primitive = compiled.rule.primitive;
755
+ if (primitive.kind === "banned-import" && sourceMatches(path4.node.source.value, primitive)) {
756
+ emit(diagnostic(file, compiled.rule, path4.node.source));
757
+ }
758
+ }
759
+ },
760
+ ExportAllDeclaration(path4) {
761
+ for (const compiled of compiledRules) {
762
+ const primitive = compiled.rule.primitive;
763
+ if (primitive.kind === "banned-import" && sourceMatches(path4.node.source.value, primitive)) {
764
+ emit(diagnostic(file, compiled.rule, path4.node.source));
765
+ }
766
+ }
767
+ },
768
+ ImportExpression(path4) {
769
+ if (!t.isStringLiteral(path4.node.source)) return;
770
+ for (const compiled of compiledRules) {
771
+ const primitive = compiled.rule.primitive;
772
+ if (primitive.kind === "banned-import" && sourceMatches(path4.node.source.value, primitive)) {
773
+ emit(diagnostic(file, compiled.rule, path4.node.source));
774
+ }
775
+ }
776
+ },
777
+ MemberExpression(path4) {
778
+ inspectResultValue(path4);
779
+ },
780
+ OptionalMemberExpression(path4) {
781
+ inspectResultValue(path4);
782
+ },
783
+ JSXExpressionContainer(path4) {
784
+ if (!path4.parentPath.isJSXElement() && !path4.parentPath.isJSXFragment()) return;
785
+ const expression = unwrapExpression(path4.node.expression);
786
+ if (!expression || !t.isExpression(expression)) return;
787
+ const name = subjectName(expression);
788
+ if (!name) return;
789
+ for (const compiled of compiledRules) {
790
+ const primitive = compiled.rule.primitive;
791
+ if (primitive.kind !== "required-wrapper-call" || primitive.context !== "jsx-child" || !hasActivatingImport(importSources, primitive.activationImports) || !compiled.subjectPattern?.test(name)) {
792
+ continue;
793
+ }
794
+ const fix = compiled.rule.fix ? jsxWrapperFix(ast, source, path4.node, expression, primitive, imports, path4) : void 0;
795
+ emit(diagnostic(file, compiled.rule, expression, { fix }));
796
+ }
797
+ },
798
+ BinaryExpression(path4) {
799
+ for (const compiled of compiledRules) {
800
+ const primitive = compiled.rule.primitive;
801
+ if (primitive.kind !== "required-wrapper-call" || primitive.context !== "numeric-operation" || !hasActivatingImport(importSources, primitive.activationImports) || primitive.operators && !primitive.operators.includes(path4.node.operator)) {
802
+ continue;
803
+ }
804
+ const operands = [path4.node.left, path4.node.right];
805
+ for (const operand of operands) {
806
+ const value = unwrapExpression(operand);
807
+ if (!value || t.isPrivateName(value)) continue;
808
+ if (t.isCallExpression(value) && t.isIdentifier(value.callee) && primitive.wrapper.syntax === "call" && bindingMatches(value.callee.name, primitive.wrapper.imports, imports)) {
809
+ continue;
810
+ }
811
+ const name = subjectName(value);
812
+ if (name && compiled.subjectPattern?.test(name)) {
813
+ emit(diagnostic(file, compiled.rule, value));
814
+ }
815
+ }
816
+ }
817
+ },
818
+ CallExpression(path4) {
819
+ inspectCall(path4.node, path4);
820
+ },
821
+ NewExpression(path4) {
822
+ inspectCall(path4.node, path4);
823
+ },
824
+ JSXOpeningElement(path4) {
825
+ const name = jsxName(path4.node.name);
826
+ if (!name) return;
827
+ for (const compiled of compiledRules) {
828
+ const primitive = compiled.rule.primitive;
829
+ if (primitive.kind === "banned-jsx-element" && primitive.elements.includes(name)) {
830
+ emit(diagnostic(file, compiled.rule, path4.node.name));
831
+ }
832
+ }
833
+ },
834
+ JSXAttribute(path4) {
835
+ if (!t.isJSXIdentifier(path4.node.name, { name: "className" })) return;
836
+ for (const fragment of classNameFragments(path4.node)) {
837
+ const tokens = fragment.value.split(/\s+/).filter(Boolean);
838
+ for (const compiled of compiledRules) {
839
+ const primitive = compiled.rule.primitive;
840
+ if (primitive.kind !== "banned-classname-pattern") continue;
841
+ const reportedTokens = [];
842
+ const fixTokens = /* @__PURE__ */ new Set();
843
+ let matchedTokens = 0;
844
+ let fixTokenLimitExceeded = false;
845
+ for (const token of tokens) {
846
+ if (!compiled.classNamePatterns?.some(
847
+ (pattern) => pattern.test(terminalClassToken(token))
848
+ )) {
849
+ continue;
850
+ }
851
+ matchedTokens += 1;
852
+ if (reportedTokens.length < MAX_REPORTED_CLASS_TOKENS && !reportedTokens.includes(token)) {
853
+ reportedTokens.push(token);
854
+ }
855
+ if (fixTokens.size < MAX_CLASS_FIX_TOKENS) fixTokens.add(token);
856
+ else if (!fixTokens.has(token)) fixTokenLimitExceeded = true;
857
+ }
858
+ if (matchedTokens === 0) continue;
859
+ const fix = compiled.rule.fix && fragment.fixNode && !fixTokenLimitExceeded ? classNameFix(source, fragment.fixNode, fragment.value, fixTokens, primitive) : void 0;
860
+ const listed = reportedTokens.sort().map((token) => {
861
+ const bounded = token.length <= 80 ? token : `${token.slice(0, 79)}\u2026`;
862
+ return `\`${bounded}\``;
863
+ }).join(", ");
864
+ const omitted = Math.max(0, matchedTokens - reportedTokens.length);
865
+ emit(
866
+ diagnostic(file, compiled.rule, fragment.node, {
867
+ message: `${compiled.rule.message} Found ${listed}${omitted > 0 ? ` and ${omitted} more` : ""}.`,
868
+ fix
869
+ })
870
+ );
871
+ }
872
+ }
873
+ }
874
+ });
875
+ } catch (error) {
876
+ if (!(error instanceof DiagnosticLimitReached)) throw error;
877
+ }
878
+ function inspectResultValue(path4) {
879
+ for (const compiled of compiledRules) {
880
+ const primitive = compiled.rule.primitive;
881
+ if (primitive.kind !== "required-result-check" || propertyName(path4.node) !== primitive.valueProperty) {
882
+ continue;
883
+ }
884
+ const object = unwrapExpression(path4.node.object);
885
+ if (!object) continue;
886
+ if (t.isIdentifier(object)) {
887
+ const binding = path4.scope.getBinding(object.name);
888
+ if (!binding?.path.isVariableDeclarator()) continue;
889
+ if (!isSdkResultInitializer(binding.path.node.init, path4, primitive, imports)) continue;
890
+ if (bindingIsNarrowedForValueAccess(path4, binding, object.name, primitive.guardProperty)) {
891
+ continue;
892
+ }
893
+ } else if (!isSdkResultInitializer(object, path4, primitive, imports)) {
894
+ continue;
895
+ }
896
+ emit(diagnostic(file, compiled.rule, path4.node));
897
+ }
898
+ }
899
+ function inspectCall(node, path4) {
900
+ for (const compiled of compiledRules) {
901
+ const primitive = compiled.rule.primitive;
902
+ if (primitive.kind === "banned-import" && t.isCallExpression(node)) {
903
+ const argument = node.arguments[0];
904
+ const sourceValue = t.isStringLiteral(argument) ? argument.value : null;
905
+ const isRequire = t.isIdentifier(node.callee, { name: "require" });
906
+ const isDynamicImport = t.isImport(node.callee);
907
+ if (sourceValue && (isRequire || isDynamicImport) && sourceMatches(sourceValue, primitive)) {
908
+ emit(diagnostic(file, compiled.rule, argument));
909
+ }
910
+ }
911
+ if (primitive.kind !== "banned-call-argument" || !callMatchesPrimitive(node, path4, primitive)) {
912
+ continue;
913
+ }
914
+ for (const index of primitive.argumentIndexes) {
915
+ const argument = node.arguments[index];
916
+ if (!argument || t.isSpreadElement(argument) || t.isJSXNamespacedName(argument)) continue;
917
+ for (const match of collectObjectPathMatches(argument, primitive)) {
918
+ const fix = compiled.rule.fix?.kind === "remove-object-property" ? removeObjectPropertyFix(source, match) : void 0;
919
+ emit(diagnostic(file, compiled.rule, match.property, { fix }));
920
+ }
921
+ }
922
+ }
923
+ }
924
+ return { diagnostics, truncated };
925
+ }
926
+ async function analyzeLintFiles(cwd, absolutePaths, rules) {
927
+ const compiledRules = compileRules(rules);
928
+ const output = [];
929
+ let remainingDiagnostics = MAX_RUN_DIAGNOSTICS;
930
+ for (let start = 0; start < absolutePaths.length; start += MAX_ANALYSIS_WORKERS) {
931
+ if (remainingDiagnostics === 0) {
932
+ for (const absolutePath of absolutePaths.slice(start)) {
933
+ output.push({
934
+ absolutePath,
935
+ file: displayPath(cwd, absolutePath),
936
+ source: "",
937
+ diagnostics: [],
938
+ truncated: true
939
+ });
940
+ }
941
+ break;
942
+ }
943
+ const allowance = Math.min(MAX_FILE_DIAGNOSTICS, remainingDiagnostics);
944
+ const batch = await Promise.all(
945
+ absolutePaths.slice(start, start + MAX_ANALYSIS_WORKERS).map(async (absolutePath) => {
946
+ const file = displayPath(cwd, absolutePath);
947
+ const source = await readBoundedSource(absolutePath);
948
+ if (source === null) {
949
+ return {
950
+ absolutePath,
951
+ file,
952
+ source: "",
953
+ diagnostics: [fileTooLargeDiagnostic(file)],
954
+ truncated: true
955
+ };
956
+ }
957
+ return {
958
+ absolutePath,
959
+ file,
960
+ source,
961
+ ...analyzeSource(source, file, compiledRules, allowance)
962
+ };
963
+ })
964
+ );
965
+ for (const file of batch) {
966
+ const diagnostics = file.diagnostics.slice(0, remainingDiagnostics);
967
+ output.push({
968
+ ...file,
969
+ diagnostics,
970
+ truncated: file.truncated || diagnostics.length < file.diagnostics.length
971
+ });
972
+ remainingDiagnostics -= diagnostics.length;
973
+ }
974
+ }
975
+ return output;
976
+ }
977
+ async function readBoundedSource(filePath) {
978
+ const handle = await promises.open(filePath, "r");
979
+ try {
980
+ const metadata = await handle.stat();
981
+ if (!metadata.isFile()) throw new TypeError(`lint source is not a regular file: ${filePath}`);
982
+ if (metadata.size > MAX_SOURCE_FILE_BYTES) return null;
983
+ const buffer = Buffer.allocUnsafe(MAX_SOURCE_FILE_BYTES + 1);
984
+ let offset = 0;
985
+ while (offset < buffer.length) {
986
+ const { bytesRead } = await handle.read(buffer, offset, buffer.length - offset, offset);
987
+ if (bytesRead === 0) break;
988
+ offset += bytesRead;
989
+ }
990
+ if (offset > MAX_SOURCE_FILE_BYTES) return null;
991
+ return buffer.subarray(0, offset).toString("utf8");
992
+ } finally {
993
+ await handle.close();
994
+ }
995
+ }
996
+ function editKey(edit) {
997
+ return `${edit.start}:${edit.end}:${edit.text}`;
998
+ }
999
+ function nonOverlappingEdits(diagnostics) {
1000
+ const editsByKey = /* @__PURE__ */ new Map();
1001
+ const plans = diagnostics.flatMap((item) => item.fix ? [item.fix] : []);
1002
+ for (const plan of plans) {
1003
+ for (const edit of plan.edits) editsByKey.set(editKey(edit), edit);
1004
+ }
1005
+ const edits = [...editsByKey.values()].sort(
1006
+ (left, right) => left.start - right.start || left.end - right.end
1007
+ );
1008
+ for (let index = 1; index < edits.length; index++) {
1009
+ const previous = edits[index - 1];
1010
+ const current = edits[index];
1011
+ if (current.start < previous.end || current.start === previous.start && current.end === previous.end) {
1012
+ return null;
1013
+ }
1014
+ }
1015
+ return { edits, fixedDiagnostics: plans.length };
1016
+ }
1017
+ var temporaryFileCounter = 0;
1018
+ async function writeAtomically(filePath, content) {
1019
+ const stat = await promises.stat(filePath);
1020
+ const temporary = path.join(
1021
+ path.dirname(filePath),
1022
+ `.${path.basename(filePath)}.cimplify-lint-${process.pid}-${temporaryFileCounter++}.tmp`
1023
+ );
1024
+ try {
1025
+ await promises.writeFile(temporary, content, { encoding: "utf8", flag: "wx", mode: stat.mode });
1026
+ await promises.rename(temporary, filePath);
1027
+ } finally {
1028
+ await promises.rm(temporary, { force: true });
1029
+ }
1030
+ }
1031
+ async function applyLintFixes(files) {
1032
+ const changedFiles = /* @__PURE__ */ new Set();
1033
+ let fixedDiagnostics = 0;
1034
+ for (const file of files) {
1035
+ const applicable = nonOverlappingEdits(file.diagnostics);
1036
+ if (!applicable || applicable.edits.length === 0) continue;
1037
+ let nextSource = file.source;
1038
+ for (const edit of [...applicable.edits].sort(
1039
+ (left, right) => right.start - left.start || right.end - left.end
1040
+ )) {
1041
+ if (edit.start < 0 || edit.end < edit.start || edit.end > nextSource.length) {
1042
+ throw new RangeError(`Invalid lint fix range for ${file.file}`);
1043
+ }
1044
+ nextSource = `${nextSource.slice(0, edit.start)}${edit.text}${nextSource.slice(edit.end)}`;
1045
+ }
1046
+ if (nextSource === file.source) continue;
1047
+ const currentSource = await promises.readFile(file.absolutePath, "utf8");
1048
+ if (currentSource !== file.source) {
1049
+ throw new Error(
1050
+ `${file.file} changed while lint fixes were being prepared; rerun the command`
1051
+ );
1052
+ }
1053
+ await writeAtomically(file.absolutePath, nextSource);
1054
+ changedFiles.add(file.absolutePath);
1055
+ fixedDiagnostics += applicable.fixedDiagnostics;
1056
+ }
1057
+ return { changedFiles, fixedDiagnostics };
1058
+ }
1059
+ var SDK_PACKAGE_NAME = "@cimplify/sdk";
1060
+ var SDK_RULE_EXPORT = "./lint-rules";
1061
+ var MAX_PACKAGE_MANIFEST_BYTES = 1024 * 1024;
1062
+ function isRecord(value) {
1063
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1064
+ }
1065
+ function decodeRuleManifest(manifest) {
1066
+ const bytes = typeof manifest.bytes === "string" ? Buffer.from(manifest.bytes, "utf8") : Buffer.from(manifest.bytes);
1067
+ if (bytes.byteLength > MAX_LINT_RULE_MANIFEST_BYTES) {
1068
+ throw new TypeError(`lint rules exceed ${MAX_LINT_RULE_MANIFEST_BYTES} bytes`);
1069
+ }
1070
+ let parsed;
1071
+ try {
1072
+ parsed = JSON.parse(bytes.toString("utf8"));
1073
+ } catch {
1074
+ throw new TypeError("lint rules are not valid JSON");
1075
+ }
1076
+ return validateLintRuleSet(parsed);
1077
+ }
1078
+ async function readBoundedRegularFile(filePath, maxBytes, label) {
1079
+ const handle = await promises.open(filePath, "r");
1080
+ try {
1081
+ const metadata = await handle.stat();
1082
+ if (!metadata.isFile()) throw new TypeError(`${label} is not a regular file`);
1083
+ if (metadata.size > maxBytes) throw new TypeError(`${label} exceeds ${maxBytes} bytes`);
1084
+ const bytes = await handle.readFile();
1085
+ if (bytes.byteLength > maxBytes) throw new TypeError(`${label} exceeds ${maxBytes} bytes`);
1086
+ return bytes;
1087
+ } finally {
1088
+ await handle.close();
1089
+ }
1090
+ }
1091
+ function pathIsInside(root, candidate) {
1092
+ const relative = path.relative(root, candidate);
1093
+ return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
1094
+ }
1095
+ function conditionalExportTarget(value, depth = 0) {
1096
+ if (typeof value === "string") return value;
1097
+ if (!isRecord(value) || depth >= 8) return null;
1098
+ for (const condition of ["import", "default", "require", "node"]) {
1099
+ const target = conditionalExportTarget(value[condition], depth + 1);
1100
+ if (target) return target;
1101
+ }
1102
+ return null;
1103
+ }
1104
+ async function readProjectSdkManifest(cwd) {
1105
+ let directory = path.resolve(cwd);
1106
+ while (true) {
1107
+ const linkedPackageRoot = path.join(directory, "node_modules", SDK_PACKAGE_NAME);
1108
+ let packageRoot;
1109
+ try {
1110
+ packageRoot = await promises.realpath(linkedPackageRoot);
1111
+ } catch (error) {
1112
+ if (error.code !== "ENOENT") throw error;
1113
+ const parent = path.dirname(directory);
1114
+ if (parent === directory) return null;
1115
+ directory = parent;
1116
+ continue;
1117
+ }
1118
+ const manifestPath = await promises.realpath(path.join(packageRoot, "package.json"));
1119
+ if (!pathIsInside(packageRoot, manifestPath)) {
1120
+ throw new TypeError(`${SDK_PACKAGE_NAME} package.json resolves outside its package`);
1121
+ }
1122
+ const manifestBytes = await readBoundedRegularFile(
1123
+ manifestPath,
1124
+ MAX_PACKAGE_MANIFEST_BYTES,
1125
+ `${SDK_PACKAGE_NAME} package.json`
1126
+ );
1127
+ let manifest;
1128
+ try {
1129
+ manifest = JSON.parse(manifestBytes.toString("utf8"));
1130
+ } catch {
1131
+ throw new TypeError(`${SDK_PACKAGE_NAME} package.json is not valid JSON`);
1132
+ }
1133
+ if (!isRecord(manifest)) {
1134
+ throw new TypeError(`${SDK_PACKAGE_NAME} package.json must be an object`);
1135
+ }
1136
+ const packageManifest = manifest;
1137
+ if (!isRecord(packageManifest.exports)) return null;
1138
+ const target = conditionalExportTarget(packageManifest.exports[SDK_RULE_EXPORT]);
1139
+ if (!target) return null;
1140
+ if (!target.startsWith("./") || path.extname(target) !== ".json") {
1141
+ throw new TypeError(`${SDK_PACKAGE_NAME} exposes ${SDK_RULE_EXPORT} as non-JSON data`);
1142
+ }
1143
+ const lexicalTarget = path.resolve(packageRoot, target);
1144
+ if (!pathIsInside(packageRoot, lexicalTarget)) {
1145
+ throw new TypeError(`${SDK_PACKAGE_NAME} exposes ${SDK_RULE_EXPORT} outside its package`);
1146
+ }
1147
+ const resolvedPath = await promises.realpath(lexicalTarget);
1148
+ if (!pathIsInside(packageRoot, resolvedPath)) {
1149
+ throw new TypeError(`${SDK_PACKAGE_NAME} exposes ${SDK_RULE_EXPORT} outside its package`);
1150
+ }
1151
+ return {
1152
+ bytes: await readBoundedRegularFile(resolvedPath, MAX_LINT_RULE_MANIFEST_BYTES, "lint rules"),
1153
+ resolvedPath
1154
+ };
1155
+ }
1156
+ }
1157
+ function invalidRuleManifest(error, source) {
1158
+ const message = error instanceof Error ? error.message : String(error);
1159
+ return new CliError(CLI_ERROR_CODE.INVALID_INPUT, `Invalid ${source} lint rules: ${message}`, {
1160
+ exitCode: 2
1161
+ });
1162
+ }
1163
+ async function loadLintRuleSet(options) {
1164
+ const loader = options.overrideManifestLoader ?? options.projectManifestLoader ?? readProjectSdkManifest;
1165
+ const source = options.overrideManifestLoader ? "override" : "project-sdk";
1166
+ try {
1167
+ const manifest = await loader(options.cwd);
1168
+ if (!manifest) {
1169
+ if (!options.overrideManifestLoader) {
1170
+ return { ruleSet: starterLintRuleSet, source: "bundled-sdk" };
1171
+ }
1172
+ throw new TypeError("override manifest is unavailable");
1173
+ }
1174
+ return {
1175
+ ruleSet: decodeRuleManifest(manifest),
1176
+ source,
1177
+ ...manifest.resolvedPath ? { resolvedPath: manifest.resolvedPath } : {}
1178
+ };
1179
+ } catch (error) {
1180
+ if (error instanceof CliError) throw error;
1181
+ throw invalidRuleManifest(error, source);
1182
+ }
1183
+ }
1184
+
1185
+ // src/lint/types.ts
1186
+ var LINT_REPORT_SCHEMA_VERSION = "1";
1187
+ var LINT_EXIT_CODE = {
1188
+ CLEAN: 0,
1189
+ DIAGNOSTICS: 1,
1190
+ FAILURE: 2
1191
+ };
1192
+
1193
+ // src/lint/run.ts
1194
+ var MAX_SERIALIZED_DIAGNOSTICS_BYTES = 192 * 1024;
1195
+ function compareDiagnostics(left, right) {
1196
+ return left.file.localeCompare(right.file) || left.range.start.offset - right.range.start.offset || left.ruleId.localeCompare(right.ruleId) || left.message.localeCompare(right.message);
1197
+ }
1198
+ function serializableDiagnostic(diagnostic2) {
1199
+ return {
1200
+ file: diagnostic2.file,
1201
+ line: diagnostic2.line,
1202
+ column: diagnostic2.column,
1203
+ ruleId: diagnostic2.ruleId,
1204
+ message: diagnostic2.message,
1205
+ fixable: diagnostic2.fixable,
1206
+ severity: diagnostic2.severity
1207
+ };
1208
+ }
1209
+ function boundSerializedDiagnostics(diagnostics) {
1210
+ const output = [];
1211
+ let bytes = 0;
1212
+ for (const diagnostic2 of diagnostics) {
1213
+ const item = serializableDiagnostic(diagnostic2);
1214
+ const itemBytes = Buffer.byteLength(JSON.stringify(item), "utf8") + 1;
1215
+ if (bytes + itemBytes > MAX_SERIALIZED_DIAGNOSTICS_BYTES) {
1216
+ return { diagnostics: output, truncated: true };
1217
+ }
1218
+ output.push(item);
1219
+ bytes += itemBytes;
1220
+ }
1221
+ return { diagnostics: output, truncated: false };
1222
+ }
1223
+ function mergeReanalyzedFiles(original, reanalyzed) {
1224
+ const replacement = new Map(reanalyzed.map((file) => [file.absolutePath, file]));
1225
+ return original.map((file) => replacement.get(file.absolutePath) ?? file);
1226
+ }
1227
+ async function runLint(options) {
1228
+ const loadedRuleSet = options.loadedRuleSet ?? await loadLintRuleSet({ cwd: options.cwd });
1229
+ const absolutePaths = await discoverLintFiles(options.cwd, options.paths ?? []);
1230
+ let analyzed;
1231
+ try {
1232
+ analyzed = await analyzeLintFiles(options.cwd, absolutePaths, loadedRuleSet.ruleSet.rules);
1233
+ } catch (error) {
1234
+ if (error instanceof SyntaxError) {
1235
+ throw new CliError(
1236
+ CLI_ERROR_CODE.INVALID_INPUT,
1237
+ `Invalid lint rule pattern: ${error.message}`,
1238
+ {
1239
+ exitCode: LINT_EXIT_CODE.FAILURE
1240
+ }
1241
+ );
1242
+ }
1243
+ throw error;
1244
+ }
1245
+ let fixed = 0;
1246
+ if (options.fix) {
1247
+ const applied = await applyLintFixes(analyzed);
1248
+ fixed = applied.fixedDiagnostics;
1249
+ if (applied.changedFiles.size > 0) {
1250
+ const reanalyzed = await analyzeLintFiles(
1251
+ options.cwd,
1252
+ [...applied.changedFiles].sort((left, right) => left.localeCompare(right)),
1253
+ loadedRuleSet.ruleSet.rules
1254
+ );
1255
+ analyzed = mergeReanalyzedFiles(analyzed, reanalyzed);
1256
+ }
1257
+ }
1258
+ const sortedDiagnostics = analyzed.flatMap((file) => file.diagnostics).sort(compareDiagnostics);
1259
+ const bounded = boundSerializedDiagnostics(sortedDiagnostics);
1260
+ const diagnostics = bounded.diagnostics;
1261
+ const errors = diagnostics.filter((item) => item.severity === "error").length;
1262
+ const warnings = diagnostics.length - errors;
1263
+ const truncated = bounded.truncated || analyzed.some((file) => file.truncated);
1264
+ const report = {
1265
+ schemaVersion: LINT_REPORT_SCHEMA_VERSION,
1266
+ ruleSet: { name: loadedRuleSet.ruleSet.name, source: loadedRuleSet.source },
1267
+ summary: { files: absolutePaths.length, errors, warnings, fixed },
1268
+ diagnostics,
1269
+ ...truncated ? { truncated: true } : {}
1270
+ };
1271
+ return {
1272
+ report,
1273
+ exitCode: diagnostics.length === 0 && !truncated ? LINT_EXIT_CODE.CLEAN : LINT_EXIT_CODE.DIAGNOSTICS
1274
+ };
1275
+ }
1276
+
1277
+ // src/commands/lint.ts
1278
+ var FORMAT_HUMAN = "human";
1279
+ var FORMAT_JSON = "json";
1280
+ var HELP = `Usage: cimplify lint [paths...] [flags]
1281
+
1282
+ Lint storefront source with the static JSON rules shipped by its installed @cimplify/sdk.
1283
+ If that SDK predates the rules asset, the CLI's bundled rule set is used.
1284
+
1285
+ Flags:
1286
+ --format human|json Output format (default: human; --json also selects JSON)
1287
+ --fix Apply mechanically safe fixes, then lint changed files again
1288
+ --help Show this help`;
1289
+ async function run(argv) {
1290
+ const args = parseArgs(argv);
1291
+ if (flagBool(args, "help")) {
1292
+ if (isJsonMode()) result({ help: HELP });
1293
+ else info(HELP);
1294
+ return;
1295
+ }
1296
+ const requestedFormat = isJsonMode() ? FORMAT_JSON : flagString(args, "format") ?? FORMAT_HUMAN;
1297
+ if (requestedFormat !== FORMAT_HUMAN && requestedFormat !== FORMAT_JSON) {
1298
+ throw new CliError(
1299
+ CLI_ERROR_CODE.INVALID_INPUT,
1300
+ `--format must be ${FORMAT_HUMAN} or ${FORMAT_JSON}; received ${JSON.stringify(requestedFormat)}.`,
1301
+ { exitCode: 2 }
1302
+ );
1303
+ }
1304
+ if (requestedFormat === FORMAT_JSON) configureOutput({ mode: OUTPUT_MODE.JSON });
1305
+ let outcome;
1306
+ try {
1307
+ outcome = await runLint({
1308
+ cwd: process.cwd(),
1309
+ paths: args.positional,
1310
+ fix: flagBool(args, "fix")
1311
+ });
1312
+ } catch (error) {
1313
+ if (error instanceof CliError) throw error;
1314
+ const message = error instanceof Error ? error.message : String(error);
1315
+ throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, `Lint could not complete: ${message}`, {
1316
+ exitCode: 2
1317
+ });
1318
+ }
1319
+ if (requestedFormat === FORMAT_JSON) {
1320
+ result(outcome.report);
1321
+ } else {
1322
+ for (const line of formatLintHuman(outcome.report)) info(line);
1323
+ }
1324
+ if (outcome.exitCode !== 0) process.exitCode = outcome.exitCode;
1325
+ }
1326
+
1327
+ export { run as default };