@sightmap/sightmap 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,650 @@
1
+ // src/parse.ts
2
+ import yaml from "js-yaml";
3
+ function parse(input, opts = {}) {
4
+ let doc;
5
+ if (typeof input === "string") {
6
+ try {
7
+ doc = yaml.load(input);
8
+ } catch (err) {
9
+ const msg = err instanceof Error ? err.message : String(err);
10
+ throw new Error(`YAML parse error: ${msg}`);
11
+ }
12
+ } else {
13
+ doc = { ...input };
14
+ }
15
+ if (doc === null || typeof doc !== "object" || Array.isArray(doc)) {
16
+ throw new Error("Expected sightmap document to be an object at the root");
17
+ }
18
+ const obj = doc;
19
+ if (obj["version"] === void 0) {
20
+ throw new Error("Missing required `version` field");
21
+ }
22
+ if (obj["version"] !== 1) {
23
+ throw new Error(`Unsupported version: ${String(obj["version"])} (expected 1)`);
24
+ }
25
+ if (Array.isArray(obj["components"])) {
26
+ obj["components"] = obj["components"].map(normalizeComponent);
27
+ }
28
+ if (Array.isArray(obj["views"])) {
29
+ obj["views"] = obj["views"].map((v) => {
30
+ const out = { ...v };
31
+ if (Array.isArray(out["components"])) {
32
+ out["components"] = out["components"].map(normalizeComponent);
33
+ }
34
+ return out;
35
+ });
36
+ }
37
+ const fragment = {
38
+ ...obj,
39
+ __brand: "SightmapFragment",
40
+ ...opts.sourceFile !== void 0 ? { __sourceFile: opts.sourceFile } : {}
41
+ };
42
+ return fragment;
43
+ }
44
+ function normalizeComponent(c) {
45
+ const sel = c.selector;
46
+ const normalized = {
47
+ ...c,
48
+ selector: typeof sel === "string" ? [sel] : sel
49
+ };
50
+ if (Array.isArray(c.children)) {
51
+ normalized.children = c.children.map(normalizeComponent);
52
+ }
53
+ return normalized;
54
+ }
55
+
56
+ // src/validate.ts
57
+ import yaml2 from "js-yaml";
58
+ import { Ajv2020 } from "ajv/dist/2020.js";
59
+
60
+ // src/diagnostics.ts
61
+ var PARSE_ERROR = "parse-error";
62
+ var SCHEMA_VALIDATION_FAILED = "schema-validation-failed";
63
+ var UNKNOWN_VERSION = "unknown-version";
64
+ var MERGE_COLLISION_VIEW = "merge-collision-view";
65
+ var MERGE_COLLISION_COMPONENT = "merge-collision-component";
66
+ var DUPLICATE_VIEW_NAME = "duplicate-view-name";
67
+ var DUPLICATE_ROUTE = "duplicate-route";
68
+ var ROUTE_SHADOWING = "route-shadowing";
69
+ var UNKNOWN_SOURCE = "unknown-source";
70
+ var SELECTOR_SYNTAX = "selector-syntax";
71
+
72
+ // src/validate.ts
73
+ import { existsSync, readFileSync } from "fs";
74
+ import { fileURLToPath } from "url";
75
+ import { resolve, dirname } from "path";
76
+ var __dirname = dirname(fileURLToPath(import.meta.url));
77
+ var schemaCandidates = [
78
+ resolve(__dirname, "./vendored/sightmap.schema.json"),
79
+ resolve(__dirname, "../vendored/sightmap.schema.json")
80
+ ];
81
+ var schemaPath = schemaCandidates.find((p) => existsSync(p));
82
+ if (schemaPath === void 0) {
83
+ throw new Error(
84
+ `vendored sightmap.schema.json not found. Looked in: ${schemaCandidates.join(", ")}`
85
+ );
86
+ }
87
+ var schema = JSON.parse(readFileSync(schemaPath, "utf8"));
88
+ var ajv = new Ajv2020({ allErrors: true, strict: false });
89
+ var ajvValidate = ajv.compile(schema);
90
+ function validate(input, opts = {}) {
91
+ const diagnostics = [];
92
+ let doc;
93
+ if (typeof input === "string") {
94
+ try {
95
+ doc = yaml2.load(input);
96
+ } catch (err) {
97
+ const e = err;
98
+ diagnostics.push({
99
+ severity: "error",
100
+ code: PARSE_ERROR,
101
+ message: e.message ?? "YAML parse error",
102
+ ...opts.sourceFile !== void 0 ? { file: opts.sourceFile } : {},
103
+ ...e.mark ? { loc: { line: (e.mark.line ?? 0) + 1, column: (e.mark.column ?? 0) + 1 } } : {}
104
+ });
105
+ return { ok: false, diagnostics };
106
+ }
107
+ } else {
108
+ doc = input;
109
+ }
110
+ if (doc === null || typeof doc !== "object" || Array.isArray(doc)) {
111
+ diagnostics.push({
112
+ severity: "error",
113
+ code: SCHEMA_VALIDATION_FAILED,
114
+ message: "Expected document root to be an object",
115
+ ...opts.sourceFile !== void 0 ? { file: opts.sourceFile } : {}
116
+ });
117
+ return { ok: false, diagnostics };
118
+ }
119
+ const obj = doc;
120
+ if (obj["version"] !== 1) {
121
+ diagnostics.push({
122
+ severity: "error",
123
+ code: UNKNOWN_VERSION,
124
+ message: obj["version"] === void 0 ? "Missing required `version` field" : `Unsupported version: ${String(obj["version"])} (expected 1)`,
125
+ ...opts.sourceFile !== void 0 ? { file: opts.sourceFile } : {},
126
+ path: "/version"
127
+ });
128
+ return { ok: false, diagnostics };
129
+ }
130
+ const ok = ajvValidate(obj);
131
+ if (!ok) {
132
+ for (const e of ajvValidate.errors ?? []) {
133
+ diagnostics.push({
134
+ severity: "error",
135
+ code: SCHEMA_VALIDATION_FAILED,
136
+ message: `${e.instancePath || "(root)"} ${e.message ?? ""}`.trim(),
137
+ ...opts.sourceFile !== void 0 ? { file: opts.sourceFile } : {},
138
+ ...e.instancePath ? { path: e.instancePath } : {}
139
+ });
140
+ }
141
+ return { ok: false, diagnostics };
142
+ }
143
+ let value;
144
+ try {
145
+ value = parse(obj, opts.sourceFile !== void 0 ? { sourceFile: opts.sourceFile } : {});
146
+ } catch (err) {
147
+ diagnostics.push({
148
+ severity: "error",
149
+ code: SCHEMA_VALIDATION_FAILED,
150
+ message: err instanceof Error ? err.message : String(err),
151
+ ...opts.sourceFile !== void 0 ? { file: opts.sourceFile } : {}
152
+ });
153
+ return { ok: false, diagnostics };
154
+ }
155
+ return { ok: true, value };
156
+ }
157
+
158
+ // src/merge.ts
159
+ function merge(fragments) {
160
+ const sorted = [...fragments].sort((a, b) => {
161
+ const aFile = a.__sourceFile ?? "";
162
+ const bFile = b.__sourceFile ?? "";
163
+ return aFile < bFile ? -1 : aFile > bFile ? 1 : 0;
164
+ });
165
+ const views = [];
166
+ const globalComponents = [];
167
+ const globalRequests = [];
168
+ const fileMemory = [];
169
+ const diagnostics = [];
170
+ const seenViewNames = /* @__PURE__ */ new Map();
171
+ const seenComponentNames = /* @__PURE__ */ new Map();
172
+ for (const f of sorted) {
173
+ const file = f.__sourceFile ?? "<unknown>";
174
+ for (const v of f.views ?? []) {
175
+ const prev = seenViewNames.get(v.name);
176
+ if (prev !== void 0) {
177
+ diagnostics.push({
178
+ severity: "warning",
179
+ code: MERGE_COLLISION_VIEW,
180
+ message: `View name "${v.name}" defined in both "${prev}" and "${file}"; first occurrence wins.`,
181
+ file
182
+ });
183
+ } else {
184
+ seenViewNames.set(v.name, file);
185
+ }
186
+ views.push(v);
187
+ }
188
+ for (const c of f.components ?? []) {
189
+ const prev = seenComponentNames.get(c.name);
190
+ if (prev !== void 0) {
191
+ diagnostics.push({
192
+ severity: "warning",
193
+ code: MERGE_COLLISION_COMPONENT,
194
+ message: `Component name "${c.name}" defined in both "${prev}" and "${file}"; first occurrence wins.`,
195
+ file
196
+ });
197
+ } else {
198
+ seenComponentNames.set(c.name, file);
199
+ }
200
+ globalComponents.push(c);
201
+ }
202
+ for (const r of f.requests ?? []) {
203
+ globalRequests.push(r);
204
+ }
205
+ if (Array.isArray(f.memory) && f.memory.length > 0) {
206
+ fileMemory.push({ memory: [...f.memory], sourceFile: file });
207
+ }
208
+ }
209
+ return {
210
+ version: 1,
211
+ views,
212
+ globalComponents,
213
+ globalRequests,
214
+ fileMemory,
215
+ diagnostics,
216
+ __brand: "Sightmap"
217
+ };
218
+ }
219
+
220
+ // src/loadDirectory.ts
221
+ import { readFile, readdir } from "fs/promises";
222
+ import { join, extname, relative, resolve as resolve2 } from "path";
223
+ async function loadDirectory(dir, opts = {}) {
224
+ const absDir = resolve2(dir);
225
+ const root = opts.diagnosticRoot !== void 0 ? resolve2(opts.diagnosticRoot) : absDir;
226
+ const files = await collectYamlFiles(absDir);
227
+ files.sort();
228
+ const fragments = [];
229
+ const loadDiagnostics = [];
230
+ for (const file of files) {
231
+ const relPath = relative(root, file);
232
+ const text = await readFile(file, "utf8");
233
+ const r = validate(text, { sourceFile: relPath });
234
+ if (r.ok) {
235
+ fragments.push(r.value);
236
+ } else {
237
+ loadDiagnostics.push(...r.diagnostics);
238
+ }
239
+ }
240
+ const merged = merge(fragments);
241
+ return {
242
+ ...merged,
243
+ diagnostics: [...loadDiagnostics, ...merged.diagnostics]
244
+ };
245
+ }
246
+ async function collectYamlFiles(dir) {
247
+ const out = [];
248
+ const entries = await readdir(dir, { withFileTypes: true });
249
+ for (const e of entries) {
250
+ const p = join(dir, e.name);
251
+ if (e.isDirectory()) {
252
+ out.push(...await collectYamlFiles(p));
253
+ } else if (e.isFile()) {
254
+ const ext = extname(p).toLowerCase();
255
+ if (ext === ".yaml" || ext === ".yml") out.push(p);
256
+ }
257
+ }
258
+ return out;
259
+ }
260
+
261
+ // src/routeMatch.ts
262
+ function canonicalizeUrl(input) {
263
+ let s = input;
264
+ const protoMatch = /^[a-z][a-z0-9+.-]*:\/\/[^/]*/i.exec(s);
265
+ if (protoMatch) {
266
+ s = s.slice(protoMatch[0].length) || "/";
267
+ }
268
+ const hash = s.indexOf("#");
269
+ if (hash !== -1) s = s.slice(0, hash);
270
+ const q = s.indexOf("?");
271
+ if (q !== -1) s = s.slice(0, q);
272
+ if (s.length > 1 && s.endsWith("/")) s = s.slice(0, -1);
273
+ return s;
274
+ }
275
+ function routeMatch(pattern, url) {
276
+ const p = normalizePattern(pattern);
277
+ const u = canonicalizeUrl(url);
278
+ const patternSegs = splitSegments(p);
279
+ const urlSegs = splitSegments(u);
280
+ return matchSegs(patternSegs, urlSegs);
281
+ }
282
+ function normalizePattern(p) {
283
+ let s = p.split("/").map((seg) => seg.startsWith(":") ? "*" : seg).join("/");
284
+ if (s.length > 1 && s.endsWith("/")) s = s.slice(0, -1);
285
+ return s;
286
+ }
287
+ function splitSegments(path) {
288
+ if (path === "/" || path === "") return [];
289
+ const trimmed = path.startsWith("/") ? path.slice(1) : path;
290
+ return trimmed.split("/");
291
+ }
292
+ function matchSegs(pattern, url) {
293
+ if (pattern.length === 0 && url.length === 0) return true;
294
+ if (pattern.length === 0) return false;
295
+ const head = pattern[0];
296
+ const rest = pattern.slice(1);
297
+ if (head === "**") {
298
+ if (rest.length === 0) return true;
299
+ for (let i = 0; i <= url.length; i++) {
300
+ if (matchSegs(rest, url.slice(i))) return true;
301
+ }
302
+ return false;
303
+ }
304
+ if (url.length === 0) return false;
305
+ if (head === "*" || head === url[0]) {
306
+ return matchSegs(rest, url.slice(1));
307
+ }
308
+ return false;
309
+ }
310
+
311
+ // src/resolver.ts
312
+ function resolveByUrl(sightmap, url, method) {
313
+ let matchedView = null;
314
+ for (const v of sightmap.views) {
315
+ if (routeMatch(v.route, url)) {
316
+ matchedView = v;
317
+ break;
318
+ }
319
+ }
320
+ const components = [];
321
+ for (const c of sightmap.globalComponents) {
322
+ components.push(...flattenComponent(c, [], "global", void 0));
323
+ }
324
+ if (matchedView !== null && Array.isArray(matchedView.components)) {
325
+ for (const c of matchedView.components) {
326
+ components.push(...flattenComponent(c, [], "view-scoped", matchedView.name));
327
+ }
328
+ }
329
+ const requests = [];
330
+ const requestPool = [
331
+ ...sightmap.globalRequests,
332
+ ...matchedView?.requests ?? []
333
+ ];
334
+ for (const req of requestPool) {
335
+ if (!routeMatch(req.route, url)) continue;
336
+ if (method !== void 0 && req.method !== void 0 && req.method !== method) continue;
337
+ requests.push(toResolvedRequest(req));
338
+ }
339
+ const memory = [];
340
+ for (const fm of sightmap.fileMemory) memory.push(...fm.memory);
341
+ if (matchedView?.memory) memory.push(...matchedView.memory);
342
+ return {
343
+ view: matchedView !== null ? toResolvedView(matchedView) : null,
344
+ components,
345
+ requests,
346
+ memory
347
+ };
348
+ }
349
+ function resolveByName(sightmap, name) {
350
+ const hits = [];
351
+ for (const v of sightmap.views) {
352
+ if (v.name === name) hits.push({ type: "view", matchedAs: "name", entry: toResolvedView(v) });
353
+ }
354
+ for (const c of sightmap.globalComponents) {
355
+ for (const rc of flattenComponent(c, [], "global", void 0)) {
356
+ if (rc.name === name) hits.push({ type: "component", matchedAs: "name", entry: rc });
357
+ }
358
+ }
359
+ for (const v of sightmap.views) {
360
+ for (const c of v.components ?? []) {
361
+ for (const rc of flattenComponent(c, [], "view-scoped", v.name)) {
362
+ if (rc.name === name) hits.push({ type: "component", matchedAs: "name", entry: rc });
363
+ }
364
+ }
365
+ }
366
+ for (const r of sightmap.globalRequests) {
367
+ if (r.name === name) hits.push({ type: "request", matchedAs: "name", entry: toResolvedRequest(r) });
368
+ }
369
+ for (const v of sightmap.views) {
370
+ for (const r of v.requests ?? []) {
371
+ if (r.name === name) hits.push({ type: "request", matchedAs: "name", entry: toResolvedRequest(r) });
372
+ }
373
+ }
374
+ return hits;
375
+ }
376
+ function resolveBySourcePath(sightmap, path) {
377
+ const hits = [];
378
+ for (const v of sightmap.views) {
379
+ if (v.source === path) hits.push({ type: "view", matchedAs: "path", entry: toResolvedView(v) });
380
+ }
381
+ for (const c of sightmap.globalComponents) {
382
+ for (const rc of flattenComponent(c, [], "global", void 0)) {
383
+ if (rc.source === path) hits.push({ type: "component", matchedAs: "path", entry: rc });
384
+ }
385
+ }
386
+ for (const v of sightmap.views) {
387
+ for (const c of v.components ?? []) {
388
+ for (const rc of flattenComponent(c, [], "view-scoped", v.name)) {
389
+ if (rc.source === path) hits.push({ type: "component", matchedAs: "path", entry: rc });
390
+ }
391
+ }
392
+ }
393
+ for (const r of sightmap.globalRequests) {
394
+ if (r.source === path) hits.push({ type: "request", matchedAs: "path", entry: toResolvedRequest(r) });
395
+ }
396
+ return hits;
397
+ }
398
+ function flattenComponent(c, parentChain, scope, scopedToView) {
399
+ const out = [];
400
+ const selector = Array.isArray(c.selector) ? c.selector : [c.selector];
401
+ out.push({
402
+ name: c.name,
403
+ selector,
404
+ ...c.source !== void 0 ? { source: c.source } : {},
405
+ ...c.description !== void 0 ? { description: c.description } : {},
406
+ memory: c.memory ? [...c.memory] : [],
407
+ parentChain: [...parentChain],
408
+ scope,
409
+ ...scopedToView !== void 0 ? { scopedToView } : {},
410
+ definedIn: { file: "<unknown>" }
411
+ });
412
+ for (const child of c.children ?? []) {
413
+ out.push(...flattenComponent(child, [...parentChain, c.name], scope, scopedToView));
414
+ }
415
+ return out;
416
+ }
417
+ function toResolvedView(v) {
418
+ return {
419
+ name: v.name,
420
+ route: v.route,
421
+ ...v.source !== void 0 ? { source: v.source } : {},
422
+ ...v.description !== void 0 ? { description: v.description } : {},
423
+ memory: v.memory ? [...v.memory] : [],
424
+ definedIn: { file: "<unknown>" }
425
+ };
426
+ }
427
+ function toResolvedRequest(r) {
428
+ return {
429
+ name: r.name,
430
+ route: r.route,
431
+ ...r.method !== void 0 ? { method: r.method } : {},
432
+ ...r.source !== void 0 ? { source: r.source } : {},
433
+ ...r.description !== void 0 ? { description: r.description } : {},
434
+ ...r.request !== void 0 ? { request: { fields: r.request.fields ?? [] } } : {},
435
+ ...r.response !== void 0 ? { response: { fields: r.response.fields ?? [] } } : {},
436
+ ...r.headers !== void 0 ? { headers: r.headers } : {},
437
+ memory: r.memory ? [...r.memory] : [],
438
+ definedIn: { file: "<unknown>" }
439
+ };
440
+ }
441
+
442
+ // src/match.ts
443
+ function match(sightmap, opts) {
444
+ return resolveByUrl(sightmap, opts.url, opts.method);
445
+ }
446
+
447
+ // src/explain.ts
448
+ function explain(sightmap, query, opts = {}) {
449
+ const byName = opts.by === "path" ? [] : resolveByName(sightmap, query);
450
+ const byPath = opts.by === "name" ? [] : resolveBySourcePath(sightmap, query);
451
+ const key = (h) => `${h.type}:${h.entry.name}`;
452
+ const namedKeys = new Set(byName.map(key));
453
+ const pathKeys = new Set(byPath.map(key));
454
+ const merged = [];
455
+ for (const h of byName) {
456
+ if (pathKeys.has(key(h))) {
457
+ merged.push(withMatchedAs(h, "name-and-path"));
458
+ } else {
459
+ merged.push(h);
460
+ }
461
+ }
462
+ for (const h of byPath) {
463
+ if (!namedKeys.has(key(h))) {
464
+ merged.push(h);
465
+ }
466
+ }
467
+ const filtered = opts.type !== void 0 ? merged.filter((h) => h.type === opts.type) : merged;
468
+ return { query, hits: filtered };
469
+ }
470
+ function withMatchedAs(h, matchedAs) {
471
+ switch (h.type) {
472
+ case "view":
473
+ return { type: "view", matchedAs, entry: h.entry };
474
+ case "component":
475
+ return { type: "component", matchedAs, entry: h.entry };
476
+ case "request":
477
+ return { type: "request", matchedAs, entry: h.entry };
478
+ }
479
+ }
480
+
481
+ // src/lintRules/duplicateName.ts
482
+ function duplicateName(sightmap) {
483
+ const seen = /* @__PURE__ */ new Map();
484
+ const out = [];
485
+ sightmap.views.forEach((v) => {
486
+ const count = (seen.get(v.name) ?? 0) + 1;
487
+ seen.set(v.name, count);
488
+ if (count > 1) {
489
+ out.push({
490
+ severity: "warning",
491
+ code: DUPLICATE_VIEW_NAME,
492
+ message: `Duplicate view name "${v.name}" (occurrence ${count}).`
493
+ });
494
+ }
495
+ });
496
+ return out;
497
+ }
498
+
499
+ // src/lintRules/duplicateRoute.ts
500
+ function duplicateRoute(sightmap) {
501
+ const seen = /* @__PURE__ */ new Map();
502
+ const out = [];
503
+ for (const v of sightmap.views) {
504
+ const prev = seen.get(v.route);
505
+ if (prev !== void 0) {
506
+ out.push({
507
+ severity: "warning",
508
+ code: DUPLICATE_ROUTE,
509
+ message: `Route "${v.route}" is defined by both "${prev}" and "${v.name}". Only "${prev}" will match.`
510
+ });
511
+ } else {
512
+ seen.set(v.route, v.name);
513
+ }
514
+ }
515
+ return out;
516
+ }
517
+
518
+ // src/lintRules/routeShadowing.ts
519
+ function routeShadowing(sightmap) {
520
+ const out = [];
521
+ const views = sightmap.views;
522
+ for (let j = 1; j < views.length; j++) {
523
+ const later = views[j];
524
+ const representative = makeRepresentativeUrl(later.route);
525
+ for (let i = 0; i < j; i++) {
526
+ const earlier = views[i];
527
+ if (earlier.route === later.route) continue;
528
+ if (routeMatch(earlier.route, representative)) {
529
+ out.push({
530
+ severity: "warning",
531
+ code: ROUTE_SHADOWING,
532
+ message: `Route "${later.route}" (view "${later.name}") is shadowed by earlier route "${earlier.route}" (view "${earlier.name}"); first-match-wins makes it unreachable.`
533
+ });
534
+ break;
535
+ }
536
+ }
537
+ }
538
+ return out;
539
+ }
540
+ function makeRepresentativeUrl(route) {
541
+ const result = route.split("/").filter((seg) => seg !== "**").map((seg) => {
542
+ if (seg === "*") return "__seg__";
543
+ if (seg.startsWith(":")) return "__seg__";
544
+ return seg;
545
+ }).join("/").replace(/^\/+/, "/");
546
+ return result || "/";
547
+ }
548
+
549
+ // src/lintRules/unknownSource.ts
550
+ import { stat } from "fs/promises";
551
+ import { resolve as resolve3 } from "path";
552
+ async function unknownSource(sightmap, opts = {}) {
553
+ const root = opts.root ?? process.cwd();
554
+ const out = [];
555
+ const seen = /* @__PURE__ */ new Set();
556
+ const check = async (source, label) => {
557
+ if (source === void 0) return;
558
+ if (seen.has(source)) return;
559
+ seen.add(source);
560
+ try {
561
+ await stat(resolve3(root, source));
562
+ } catch {
563
+ out.push({
564
+ severity: "warning",
565
+ code: UNKNOWN_SOURCE,
566
+ message: `${label}: source path "${source}" does not exist on disk (relative to ${root}).`
567
+ });
568
+ }
569
+ };
570
+ for (const v of sightmap.views) {
571
+ await check(v.source, `view "${v.name}"`);
572
+ for (const c of v.components ?? []) {
573
+ await walkComponent(c, `component "${c.name}"`, check);
574
+ }
575
+ }
576
+ for (const c of sightmap.globalComponents) {
577
+ await walkComponent(c, `component "${c.name}"`, check);
578
+ }
579
+ for (const r of sightmap.globalRequests) {
580
+ await check(r.source, `request "${r.name}"`);
581
+ }
582
+ return out;
583
+ }
584
+ async function walkComponent(c, label, check) {
585
+ await check(c.source, label);
586
+ for (const child of c.children ?? []) {
587
+ await walkComponent(child, `component "${child.name}"`, check);
588
+ }
589
+ }
590
+
591
+ // src/lintRules/selectorSyntax.ts
592
+ import { parse as parseSelector } from "css-what";
593
+ function selectorSyntax(sightmap) {
594
+ const out = [];
595
+ const visit = (c, scope) => {
596
+ const selectors = Array.isArray(c.selector) ? c.selector : [c.selector];
597
+ selectors.forEach((sel, i) => {
598
+ try {
599
+ parseSelector(sel);
600
+ } catch (err) {
601
+ const msg = err instanceof Error ? err.message : String(err);
602
+ out.push({
603
+ severity: "warning",
604
+ code: SELECTOR_SYNTAX,
605
+ message: `${scope}: selector at index ${i} ("${sel}") failed to parse: ${msg}`
606
+ });
607
+ }
608
+ });
609
+ for (const child of c.children ?? []) visit(child, `component "${child.name}"`);
610
+ };
611
+ for (const c of sightmap.globalComponents) visit(c, `component "${c.name}"`);
612
+ for (const v of sightmap.views) {
613
+ for (const c of v.components ?? []) visit(c, `component "${c.name}"`);
614
+ }
615
+ return out;
616
+ }
617
+
618
+ // src/lint.ts
619
+ async function lint(sightmap, opts = {}) {
620
+ const enabled = (code) => opts.rules?.[code] !== false;
621
+ const out = [];
622
+ if (enabled("duplicate-view-name")) out.push(...duplicateName(sightmap));
623
+ if (enabled("duplicate-route")) out.push(...duplicateRoute(sightmap));
624
+ if (enabled("route-shadowing")) out.push(...routeShadowing(sightmap));
625
+ if (enabled("selector-syntax")) out.push(...selectorSyntax(sightmap));
626
+ if (enabled("unknown-source")) {
627
+ out.push(...await unknownSource(sightmap, opts.root !== void 0 ? { root: opts.root } : {}));
628
+ }
629
+ return out;
630
+ }
631
+ export {
632
+ DUPLICATE_ROUTE,
633
+ DUPLICATE_VIEW_NAME,
634
+ MERGE_COLLISION_COMPONENT,
635
+ MERGE_COLLISION_VIEW,
636
+ PARSE_ERROR,
637
+ ROUTE_SHADOWING,
638
+ SCHEMA_VALIDATION_FAILED,
639
+ SELECTOR_SYNTAX,
640
+ UNKNOWN_SOURCE,
641
+ UNKNOWN_VERSION,
642
+ explain,
643
+ lint,
644
+ loadDirectory,
645
+ match,
646
+ merge,
647
+ parse,
648
+ validate
649
+ };
650
+ //# sourceMappingURL=index.js.map