@navecss/core 0.4.0 → 0.6.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.
@@ -1,1089 +0,0 @@
1
- import { reportDiagnostics, tokenize, matchBrackets, createPositionFinder, readItem, isNaveAtKeyword, atKeywordName, OPENERS } from './chunk-NFS2REPQ.js';
2
- import { decodeIncomingMap, MappingsBuilder } from './chunk-SYNQJNVB.js';
3
- import { atoms } from './chunk-CUL5PLPI.js';
4
-
5
- // src/directive/expand-text-output.ts
6
- function toOriginal(position, incoming) {
7
- if (!incoming) return position;
8
- const original = incoming.originalPositionFor(position);
9
- return original ? { line: original.line, column: original.column, sourceIndex: original.sourceIndex } : void 0;
10
- }
11
- function appendMappedRange(ctx, range, sourceForToken) {
12
- const { builder, output, incoming } = ctx;
13
- const { text, rangeStart, rangeEnd, tokensInRange } = range;
14
- let cursor = rangeStart;
15
- for (const token of tokensInRange) {
16
- const before = text.slice(cursor, token.startIndex);
17
- output.push(before);
18
- builder.advance(before);
19
- const source = toOriginal(sourceForToken(token), incoming);
20
- if (source) builder.mark(source);
21
- const body = text.slice(token.startIndex, token.endIndex);
22
- output.push(body);
23
- builder.advance(body);
24
- cursor = token.endIndex;
25
- }
26
- const rest = text.slice(cursor, rangeEnd);
27
- output.push(rest);
28
- builder.advance(rest);
29
- }
30
- function tokensBetween(tokens, cursor, start, end) {
31
- let i = cursor.index;
32
- while (i < tokens.length && tokens[i].startIndex < start) i++;
33
- const result = [];
34
- while (i < tokens.length && tokens[i].startIndex < end) {
35
- result.push(tokens[i]);
36
- i++;
37
- }
38
- cursor.index = i;
39
- return result;
40
- }
41
- function appendGap(ctx, gap) {
42
- appendMappedRange(
43
- ctx,
44
- {
45
- text: gap.css,
46
- rangeStart: gap.start,
47
- rangeEnd: gap.end,
48
- tokensInRange: tokensBetween(gap.tokens, ctx.gapTokenCursor, gap.start, gap.end)
49
- },
50
- (t) => gap.positionAt(t.startIndex)
51
- );
52
- }
53
- function appendEditParts(ctx, edit) {
54
- for (const part of edit.parts) {
55
- const partTokens = tokenize(part.text);
56
- appendMappedRange(
57
- ctx,
58
- { text: part.text, rangeStart: 0, rangeEnd: part.text.length, tokensInRange: partTokens },
59
- () => part.source
60
- );
61
- }
62
- }
63
- function buildOutput(input) {
64
- const incoming = input.inputSourceMap ? decodeIncomingMap(input.inputSourceMap) : void 0;
65
- const sorted = [...input.edits].toSorted((a, b) => a.start - b.start || a.end - b.end);
66
- const ctx = {
67
- builder: new MappingsBuilder(),
68
- output: [],
69
- incoming,
70
- gapTokenCursor: { index: 0 }
71
- };
72
- let cursor = 0;
73
- for (const edit of sorted) {
74
- appendGap(ctx, {
75
- css: input.css,
76
- tokens: input.tokens,
77
- start: cursor,
78
- end: edit.start,
79
- positionAt: input.positionAt
80
- });
81
- appendEditParts(ctx, edit);
82
- cursor = Math.max(cursor, edit.end);
83
- }
84
- appendGap(ctx, {
85
- css: input.css,
86
- tokens: input.tokens,
87
- start: cursor,
88
- end: input.css.length,
89
- positionAt: input.positionAt
90
- });
91
- const sources = incoming ? [...incoming.sources] : [input.from ?? "<input>"];
92
- const map = {
93
- version: 3,
94
- sources,
95
- names: [],
96
- mappings: ctx.builder.toMappings(),
97
- // Kept as authored, not re-derived: the incoming map's own sourceRoot
98
- // and sourcesContent describe ITS sources, unrelated to anything this
99
- // pass computes, and dropping them silently would strand a devtools
100
- // "view source" that only ever worked because they were there.
101
- ...incoming?.sourceRoot !== void 0 && { sourceRoot: incoming.sourceRoot },
102
- ...incoming?.sourcesContent !== void 0 && { sourcesContent: incoming.sourcesContent }
103
- };
104
- return { css: ctx.output.join(""), map: JSON.stringify(map) };
105
- }
106
-
107
- // src/directive/group-at-rules.ts
108
- var WORKAROUND_GROUP_AT_RULE_NAMES = /* @__PURE__ */ new Set([
109
- "container",
110
- "layer",
111
- "media",
112
- "scope",
113
- "starting-style",
114
- "supports"
115
- ]);
116
-
117
- // src/directive/expand-text-context.ts
118
- function isKeyframesName(name) {
119
- return /keyframes$/i.test(name);
120
- }
121
- function childContext(tokens, item, context) {
122
- const { isInsideKeyframes, hasStyleRuleAncestor } = context;
123
- if (item.kind === "rule") {
124
- return {
125
- isStyleRuleParent: true,
126
- isInsideKeyframes,
127
- hasStyleRuleAncestor: true,
128
- isWorkaroundGroup: false
129
- };
130
- }
131
- const name = atKeywordName(tokens[item.start]);
132
- const isKeyframes = isInsideKeyframes || isKeyframesName(name);
133
- return {
134
- isStyleRuleParent: false,
135
- isInsideKeyframes: isKeyframes,
136
- hasStyleRuleAncestor,
137
- isWorkaroundGroup: WORKAROUND_GROUP_AT_RULE_NAMES.has(name) && !isKeyframes && hasStyleRuleAncestor
138
- };
139
- }
140
-
141
- // src/directive/expand-text-eof-close.ts
142
- function hasUnescapedTrailingChar(raw, char) {
143
- if (raw.length < 2 || raw.at(-1) !== char) return false;
144
- let backslashes = 0;
145
- for (let i = raw.length - 2; i >= 0 && raw[i] === "\\"; i--) backslashes++;
146
- return backslashes % 2 === 0;
147
- }
148
- function trailingBackslashCount(raw) {
149
- let count = 0;
150
- for (let i = raw.length - 1; i >= 0 && raw[i] === "\\"; i--) count++;
151
- return count;
152
- }
153
- function lexicalEofCloser(token) {
154
- if (token.type === "string-token") {
155
- const quote = token.raw[0];
156
- if (quote === void 0 || hasUnescapedTrailingChar(token.raw, quote)) return void 0;
157
- const hasDanglingEscape = trailingBackslashCount(token.raw) % 2 === 1;
158
- return hasDanglingEscape ? `\\${quote}` : quote;
159
- }
160
- if (token.type === "url-token" || token.type === "bad-url-token") {
161
- if (token.raw.endsWith(")")) return void 0;
162
- return trailingBackslashCount(token.raw) % 2 === 1 ? String.raw`\)` : ")";
163
- }
164
- return void 0;
165
- }
166
- function bracketEofCloser(type) {
167
- if (type === "{-token") return "}";
168
- if (type === "[-token") return "]";
169
- return ")";
170
- }
171
- function scanFrameTail(w, start, limit) {
172
- const unmatchedOpeners = [];
173
- let hasDanglingText = false;
174
- let hasOpenColon = false;
175
- let i = start;
176
- while (i < limit) {
177
- const token = w.tokens[i];
178
- const type = token.type;
179
- if (type !== "whitespace-token" && type !== "comment") hasDanglingText = true;
180
- if (type === "semicolon-token") {
181
- hasOpenColon = false;
182
- } else if (type === "colon-token") {
183
- hasOpenColon = true;
184
- } else if (OPENERS.has(type)) {
185
- const closeIndex = w.closerFor[i];
186
- if (closeIndex === -1 || closeIndex >= limit) {
187
- unmatchedOpeners.push(i);
188
- } else {
189
- i = closeIndex;
190
- }
191
- }
192
- i++;
193
- }
194
- return { unmatchedOpeners: unmatchedOpeners.toReversed(), hasDanglingText, hasOpenColon };
195
- }
196
- function eofClosers(w, start, limit, last) {
197
- const closers = [];
198
- if (last) {
199
- const lexical = lexicalEofCloser(last);
200
- if (lexical) closers.push(lexical);
201
- }
202
- const scan = scanFrameTail(w, start, limit);
203
- for (const openIndex of scan.unmatchedOpeners) {
204
- closers.push(bracketEofCloser(w.tokens[openIndex].type));
205
- }
206
- if (scan.hasOpenColon) {
207
- closers.push(";");
208
- } else if (closers.length === 0 && scan.hasDanglingText) {
209
- closers.push("{}");
210
- }
211
- return closers.join("");
212
- }
213
- function closeInfoFor(w, frameStart, blockEndIndex, scanLimit) {
214
- const closer = w.tokens[blockEndIndex];
215
- if (closer) return { position: closer.startIndex, prefix: "" };
216
- const last = scanLimit > frameStart ? w.tokens[scanLimit - 1] : void 0;
217
- if (last?.type === "comment") {
218
- const isClosed = last.raw.length >= 4 && last.raw.endsWith("*/");
219
- return { position: isClosed ? w.css.length : last.startIndex, prefix: "" };
220
- }
221
- return { position: w.css.length, prefix: eofClosers(w, frameStart, scanLimit, last) };
222
- }
223
-
224
- // src/directive/expand-text-render.ts
225
- function withoutLineTerminators(value) {
226
- return value.replaceAll(/\r\n|[\r\n\f]/g, " ");
227
- }
228
- function renderDeclarations(decls) {
229
- return decls.map((d) => `${d.prop}: ${withoutLineTerminators(d.value)}`).join("; ");
230
- }
231
- function renderRule(selector, decls) {
232
- return `${selector} { ${renderDeclarations(decls)} }`;
233
- }
234
- function renderBlock(block) {
235
- if (block.kind === "pseudo") return renderRule(block.selector, block.declarations);
236
- const inner = [];
237
- if (block.declarations.length > 0) inner.push(renderRule("&", block.declarations));
238
- for (const pseudo of block.pseudos) inner.push(renderRule(pseudo.selector, pseudo.declarations));
239
- return `@${block.kind} ${block.condition} { ${inner.join(" ")} }`;
240
- }
241
- function renderInline(declarations, isWrapped) {
242
- if (declarations.length === 0) return "";
243
- if (isWrapped) return renderRule("&", declarations);
244
- return `${renderDeclarations(declarations)};`;
245
- }
246
-
247
- // src/selector-utils.ts
248
- function isCommentClose(text, index, commentStart) {
249
- return text[index] === "/" && text[index - 1] === "*" && index - 1 >= commentStart + 2;
250
- }
251
- function isConsumedAsLiteral(state, text, index) {
252
- if (state.commentStart !== void 0) {
253
- if (isCommentClose(text, index, state.commentStart)) state.commentStart = void 0;
254
- return true;
255
- }
256
- if (state.isEscaped) {
257
- state.isEscaped = false;
258
- return true;
259
- }
260
- if (text[index] === "\\") {
261
- state.isEscaped = true;
262
- return true;
263
- }
264
- if (!state.quote) return false;
265
- if (text[index] === state.quote) state.quote = void 0;
266
- return true;
267
- }
268
- function isCodeChar(state, text, index) {
269
- if (isConsumedAsLiteral(state, text, index)) return false;
270
- const char = text[index];
271
- if (char === '"' || char === "'") {
272
- state.quote = char;
273
- return false;
274
- }
275
- if (char === "/" && text[index + 1] === "*") {
276
- state.commentStart = index;
277
- return false;
278
- }
279
- return true;
280
- }
281
- function codeMask(text) {
282
- const state = { quote: void 0, isEscaped: false, commentStart: void 0 };
283
- return Array.from({ length: text.length }, (_, index) => isCodeChar(state, text, index));
284
- }
285
- function splitTopLevel(selectorList) {
286
- const mask = codeMask(selectorList);
287
- const branches = [];
288
- let depth = 0;
289
- let start = 0;
290
- for (const [index, isCode] of mask.entries()) {
291
- if (!isCode) continue;
292
- const char = selectorList[index];
293
- if (char === "(" || char === "[") depth += 1;
294
- else if (char === ")" || char === "]") depth -= 1;
295
- else if (char === "," && depth === 0) {
296
- branches.push(selectorList.slice(start, index));
297
- start = index + 1;
298
- }
299
- }
300
- return [...branches, selectorList.slice(start)];
301
- }
302
- function hasNestingSelector(branch) {
303
- return codeMask(branch).some((isCode, index) => isCode && branch[index] === "&");
304
- }
305
- function anchorSelectorList(selectorList) {
306
- return splitTopLevel(selectorList).map((branch) => {
307
- const trimmed = branch.trim();
308
- if (trimmed === "") {
309
- throw new Error(
310
- `anchorSelectorList: empty selector branch in pseudo key "${selectorList}". Remove the stray or trailing comma: an empty branch would otherwise compile to a bare "&", matching the atom unconditionally.`
311
- );
312
- }
313
- return hasNestingSelector(trimmed) ? trimmed : `&${trimmed}`;
314
- }).join(", ");
315
- }
316
-
317
- // src/directive/prelude-components.ts
318
- var OPENERS2 = /* @__PURE__ */ new Set(["(-token", "[-token", "function-token", "{-token"]);
319
- var CLOSERS = /* @__PURE__ */ new Set([")-token", "]-token", "}-token"]);
320
- function findGroupEnd(tokens, start) {
321
- let depth = 1;
322
- let i = start + 1;
323
- while (i < tokens.length && depth > 0) {
324
- if (OPENERS2.has(tokens[i].type)) depth++;
325
- else if (CLOSERS.has(tokens[i].type)) depth--;
326
- i++;
327
- }
328
- return i;
329
- }
330
- function readGroup(tokens, start, prelude) {
331
- const end = findGroupEnd(tokens, start);
332
- const last = tokens[end - 1] ?? tokens[start];
333
- return {
334
- kind: "bad-token",
335
- text: prelude.slice(tokens[start].startIndex, last.endIndex),
336
- offset: tokens[start].startIndex,
337
- endOffset: last.endIndex
338
- };
339
- }
340
- function isBangIdent(tokens, i) {
341
- const t = tokens[i];
342
- return t.type === "delim-token" && t.structured?.value === "!" && tokens[i + 1]?.type === "ident-token";
343
- }
344
- function readBangIdent(tokens, i) {
345
- const bang = tokens[i];
346
- const ident = tokens[i + 1];
347
- return {
348
- kind: "bad-token",
349
- text: bang.raw + ident.raw,
350
- offset: bang.startIndex,
351
- endOffset: ident.endIndex
352
- };
353
- }
354
- function readSingleToken(t) {
355
- if (t.type === "ident-token") {
356
- return {
357
- kind: "ident",
358
- name: t.structured?.value,
359
- offset: t.startIndex,
360
- endOffset: t.endIndex
361
- };
362
- }
363
- return { kind: "bad-token", text: t.raw, offset: t.startIndex, endOffset: t.endIndex };
364
- }
365
- function readPreludeComponents(prelude) {
366
- const tokens = tokenize(prelude).filter(
367
- (t) => t.type !== "whitespace-token" && t.type !== "comment"
368
- );
369
- const components = [];
370
- let i = 0;
371
- while (i < tokens.length) {
372
- if (OPENERS2.has(tokens[i].type)) {
373
- components.push(readGroup(tokens, i, prelude));
374
- i = findGroupEnd(tokens, i);
375
- continue;
376
- }
377
- if (isBangIdent(tokens, i)) {
378
- components.push(readBangIdent(tokens, i));
379
- i += 2;
380
- continue;
381
- }
382
- components.push(readSingleToken(tokens[i]));
383
- i++;
384
- }
385
- return components;
386
- }
387
-
388
- // src/directive/plain-object.ts
389
- var BOXES = [
390
- ["String", String.prototype],
391
- ["Number", Number.prototype],
392
- ["Boolean", Boolean.prototype],
393
- ["BigInt", BigInt.prototype],
394
- ["Symbol", Symbol.prototype]
395
- ];
396
- function boxedPrimitiveKind(value) {
397
- if (typeof value !== "object" || value === null) return void 0;
398
- for (const [name, prototype] of BOXES) {
399
- try {
400
- prototype.valueOf.call(value);
401
- return name;
402
- } catch {
403
- }
404
- }
405
- try {
406
- const prototype = Object.getPrototypeOf(value);
407
- return BOXES.find(([, box]) => box === prototype)?.[0];
408
- } catch {
409
- return void 0;
410
- }
411
- }
412
- function unboxedPrimitive(value) {
413
- for (const [, prototype] of BOXES) {
414
- try {
415
- return prototype.valueOf.call(value);
416
- } catch {
417
- }
418
- }
419
- return void 0;
420
- }
421
- function isPlainObject(value) {
422
- return typeof value === "object" && value !== null && !Array.isArray(value) && boxedPrimitiveKind(value) === void 0;
423
- }
424
-
425
- // src/directive/resolve.ts
426
- function toDeclarations(decls) {
427
- return Object.entries(decls).map(([prop, value]) => ({ prop, value }));
428
- }
429
- function isDeclarationsObject(value) {
430
- return isPlainObject(value);
431
- }
432
- function resolveDeclarations(name, atom) {
433
- if (!isDeclarationsObject(atom.declarations)) {
434
- throw new Error(`@nave: atom "${name}" is registered without a declarations object`);
435
- }
436
- return toDeclarations(atom.declarations);
437
- }
438
- function resolvePseudoBlocks(pseudos) {
439
- if (!pseudos) return [];
440
- return Object.entries(pseudos).filter(([, decls]) => decls).map(([selector, decls]) => ({
441
- kind: "pseudo",
442
- selector,
443
- declarations: toDeclarations(decls)
444
- }));
445
- }
446
- function resolveConditionalBlocks(kind, blocks) {
447
- if (!blocks) return [];
448
- const result = [];
449
- for (const [condition, block] of Object.entries(blocks)) {
450
- if (!block) continue;
451
- const declarations = block.declarations ? toDeclarations(block.declarations) : [];
452
- const pseudos = resolvePseudoBlocks(block.pseudos);
453
- if (declarations.length === 0 && pseudos.length === 0) continue;
454
- result.push({ kind, condition, declarations, pseudos });
455
- }
456
- return result;
457
- }
458
- function resolveAtom(name, atom) {
459
- return {
460
- declarations: resolveDeclarations(name, atom),
461
- blocks: [
462
- ...resolvePseudoBlocks(atom.pseudos),
463
- ...resolveConditionalBlocks("media", atom.media),
464
- ...resolveConditionalBlocks("container", atom.container)
465
- ]
466
- };
467
- }
468
- function lookupAtom(name, extend) {
469
- if (Object.hasOwn(extend, name)) return extend[name] ?? void 0;
470
- if (Object.hasOwn(atoms, name)) return atoms[name];
471
- return void 0;
472
- }
473
- function resolve(names, options = {}) {
474
- const extend = options.extend ?? {};
475
- const resolved = /* @__PURE__ */ Object.create(null);
476
- const unresolved = [];
477
- for (const name of names) {
478
- const atom = lookupAtom(name, extend);
479
- if (!atom) {
480
- unresolved.push(name);
481
- continue;
482
- }
483
- resolved[name] = resolveAtom(name, atom);
484
- }
485
- return { resolved, unresolved };
486
- }
487
-
488
- // src/directive/plan.ts
489
- function anchorBlock(block) {
490
- if (block.kind === "pseudo") {
491
- return {
492
- kind: "pseudo",
493
- selector: anchorSelectorList(block.selector),
494
- declarations: block.declarations
495
- };
496
- }
497
- return {
498
- kind: block.kind,
499
- condition: block.condition,
500
- declarations: block.declarations,
501
- pseudos: block.pseudos.map((p) => ({ ...p, selector: anchorSelectorList(p.selector) }))
502
- };
503
- }
504
- function emptyResult(diagnostics) {
505
- return { declarations: [], wrapInAmpersand: false, blocks: [], diagnostics };
506
- }
507
- function readNames(components) {
508
- const names = [];
509
- const diagnostics = [];
510
- for (const component of components) {
511
- if (component.kind === "ident") {
512
- names.push(component.name);
513
- continue;
514
- }
515
- diagnostics.push({
516
- code: "bad-token",
517
- text: component.text,
518
- offset: component.offset,
519
- endOffset: component.endOffset
520
- });
521
- }
522
- return { names, diagnostics };
523
- }
524
- function readUnknownAtomDiagnostics(components, resolved) {
525
- const diagnostics = [];
526
- for (const component of components) {
527
- if (component.kind !== "ident" || Object.hasOwn(resolved, component.name)) continue;
528
- diagnostics.push({
529
- code: "unknown-atom",
530
- name: component.name,
531
- offset: component.offset,
532
- endOffset: component.endOffset
533
- });
534
- }
535
- return diagnostics;
536
- }
537
- function planNames(prelude, options) {
538
- const components = readPreludeComponents(prelude);
539
- if (components.length === 0) {
540
- return emptyResult([{ code: "no-atom", offset: 0, endOffset: prelude.length }]);
541
- }
542
- const { names, diagnostics } = readNames(components);
543
- const { resolved } = resolve(names, { extend: options.extend });
544
- diagnostics.push(...readUnknownAtomDiagnostics(components, resolved));
545
- diagnostics.sort((a, b) => a.offset - b.offset);
546
- const declarations = [];
547
- const blocks = [];
548
- for (const name of names) {
549
- const atom = Object.hasOwn(resolved, name) ? resolved[name] : void 0;
550
- if (!atom) continue;
551
- declarations.push(...atom.declarations);
552
- blocks.push(...atom.blocks.map((block) => anchorBlock(block)));
553
- }
554
- return { declarations, wrapInAmpersand: false, blocks, diagnostics };
555
- }
556
- function plan(prelude, context, options = {}) {
557
- if (!context.isStyleRuleParent) {
558
- return emptyResult([{ code: "bad-parent", offset: 0, endOffset: prelude.length }]);
559
- }
560
- if (context.isInsideKeyframes) {
561
- return emptyResult([{ code: "in-keyframes", offset: 0, endOffset: prelude.length }]);
562
- }
563
- const result = planNames(prelude, options);
564
- return { ...result, wrapInAmpersand: context.isFollowingNestedNode };
565
- }
566
-
567
- // src/directive/expand-text-walk.ts
568
- var Walker = class {
569
- closerFor;
570
- css;
571
- diagnostics = [];
572
- edits = [];
573
- // Built once per call, not once per query: `positionAt` runs once per
574
- // output token, and a fresh linear scan on every call made the whole
575
- // pass quadratic in the stylesheet's size. Not private: this
576
- // project's class-member-order and class-sort lint rules disagree with
577
- // each other on where a private field goes relative to the surrounding
578
- // public ones, which a field with no access modifier sidesteps.
579
- // How far back an EOF-closing frame may look for its own unmatched
580
- // brackets/declaration: shrinks to a frame's own start every time that
581
- // frame writes its own EOF closers, so an ancestor frame further out
582
- // never rediscovers — and re-closes — the same inner construct. Starts
583
- // at `tokens.length`, unbounded, since nothing has closed anything yet.
584
- eofScanLimit;
585
- findPosition;
586
- options;
587
- tokens;
588
- constructor(css, options) {
589
- this.css = css;
590
- this.tokens = tokenize(css);
591
- this.closerFor = matchBrackets(this.tokens);
592
- this.options = options;
593
- this.findPosition = createPositionFinder(css);
594
- this.eofScanLimit = this.tokens.length;
595
- }
596
- positionAt(offset) {
597
- return this.findPosition(offset);
598
- }
599
- };
600
- function isStructural(token) {
601
- return token.type !== "whitespace-token" && token.type !== "comment";
602
- }
603
- function skipInert(tokens, i, limit) {
604
- let j = i;
605
- while (j < limit && !isStructural(tokens[j])) j++;
606
- return j;
607
- }
608
- function repositionDiagnostics(diagnostics, item, directiveStart, directiveEnd) {
609
- const preludeOffset = item.preludeOffset ?? 0;
610
- return diagnostics.map((d) => {
611
- if (d.code === "unknown-atom" || d.code === "bad-token") {
612
- return { ...d, offset: preludeOffset + d.offset, endOffset: preludeOffset + d.endOffset };
613
- }
614
- return { ...d, offset: directiveStart, endOffset: directiveEnd };
615
- });
616
- }
617
- function markNestedGroup(diagnostics) {
618
- for (const [i, d] of diagnostics.entries()) {
619
- if (d.code === "bad-parent") diagnostics[i] = { ...d, detail: "nested-group" };
620
- }
621
- }
622
- function processDirective(w, site) {
623
- const { item, context, hasNestedNode, frame } = site;
624
- const directiveStart = w.tokens[item.start].startIndex;
625
- const directiveEnd = w.tokens[item.end - 1].endIndex;
626
- const source = w.positionAt(directiveStart);
627
- const prelude = w.css.slice(item.preludeOffset ?? 0, item.preludeEndOffset ?? 0);
628
- const result = plan(
629
- prelude,
630
- {
631
- isStyleRuleParent: context.isStyleRuleParent,
632
- isInsideKeyframes: context.isInsideKeyframes,
633
- isFollowingNestedNode: hasNestedNode
634
- },
635
- { extend: w.options.extend }
636
- );
637
- const positioned = repositionDiagnostics(result.diagnostics, item, directiveStart, directiveEnd);
638
- if (context.isWorkaroundGroup) markNestedGroup(positioned);
639
- if (item.blockStart !== void 0)
640
- positioned.push({ code: "has-block", offset: directiveStart, endOffset: directiveEnd });
641
- w.diagnostics.push(...positioned);
642
- const inline = renderInline(result.declarations, result.wrapInAmpersand);
643
- w.edits.push({
644
- start: directiveStart,
645
- end: directiveEnd,
646
- parts: inline === "" ? [] : [{ text: inline, source }]
647
- });
648
- for (const block of result.blocks) frame.parts.push({ text: ` ${renderBlock(block)}`, source });
649
- }
650
- function isNestedNode(tokens, item) {
651
- if (item.kind === "rule") return true;
652
- if (item.kind !== "at-rule") return false;
653
- return !isNaveAtKeyword(tokens[item.start]);
654
- }
655
- function flushFrame(w, frameStart, blockEndIndex, frame) {
656
- if (frame.parts.length === 0) return;
657
- const close = closeInfoFor(w, frameStart, blockEndIndex, w.eofScanLimit);
658
- if (w.tokens[blockEndIndex] === void 0) w.eofScanLimit = frameStart;
659
- const parts = close.prefix === "" ? frame.parts : [{ text: close.prefix, source: w.positionAt(close.position) }, ...frame.parts];
660
- w.edits.push({ start: close.position, end: close.position, parts });
661
- }
662
- function toWalkFrame(w, bounds) {
663
- return {
664
- i: skipInert(w.tokens, bounds.start, bounds.limit),
665
- hasNestedNode: false,
666
- limit: bounds.limit,
667
- start: bounds.start,
668
- context: bounds.context,
669
- frame: { parts: [] }
670
- };
671
- }
672
- function childFrameFor(w, item, context) {
673
- if (item.blockStart === void 0 || item.blockEnd === void 0) return void 0;
674
- return toWalkFrame(w, {
675
- start: item.blockStart,
676
- limit: item.blockEnd,
677
- context: childContext(w.tokens, item, context)
678
- });
679
- }
680
- function walkBlock(w, bounds) {
681
- const stack = [toWalkFrame(w, bounds)];
682
- while (stack.length > 0) {
683
- const top = stack.at(-1);
684
- if (top.i >= top.limit) {
685
- flushFrame(w, top.start, top.limit, top.frame);
686
- stack.pop();
687
- continue;
688
- }
689
- const item = readItem(w.tokens, top.i, top.limit, w.closerFor);
690
- top.i = skipInert(w.tokens, item.end, top.limit);
691
- if (item.kind === "at-rule" && isNaveAtKeyword(w.tokens[item.start])) {
692
- processDirective(w, {
693
- item,
694
- context: top.context,
695
- hasNestedNode: top.hasNestedNode,
696
- frame: top.frame
697
- });
698
- } else if (item.kind === "rule" || item.kind === "at-rule") {
699
- const child = childFrameFor(w, item, top.context);
700
- if (child) stack.push(child);
701
- }
702
- if (isNestedNode(w.tokens, item)) top.hasNestedNode = true;
703
- }
704
- }
705
-
706
- // src/directive/expand-text.ts
707
- function expandText(css, options = {}) {
708
- const w = new Walker(css, options);
709
- walkBlock(w, {
710
- start: 0,
711
- limit: w.tokens.length,
712
- context: {
713
- isStyleRuleParent: false,
714
- isInsideKeyframes: false,
715
- hasStyleRuleAncestor: false,
716
- isWorkaroundGroup: false
717
- }
718
- });
719
- const { css: outputCss, map } = buildOutput({
720
- css: w.css,
721
- tokens: w.tokens,
722
- edits: w.edits,
723
- from: options.from,
724
- inputSourceMap: options.inputSourceMap,
725
- positionAt: (offset) => w.positionAt(offset)
726
- });
727
- return { css: outputCss, map, diagnostics: reportDiagnostics(w.diagnostics, css, options) };
728
- }
729
-
730
- // src/snapshot-extend-atoms.ts
731
- function snapshotDeclarations(declarations) {
732
- if (!isPlainObject(declarations)) return declarations;
733
- const out = /* @__PURE__ */ Object.create(null);
734
- for (const [prop, value] of Object.entries(declarations)) {
735
- out[prop] = typeof value === "string" ? value : String(value);
736
- }
737
- return out;
738
- }
739
- function snapshotPseudos(pseudos) {
740
- if (!isPlainObject(pseudos)) return pseudos;
741
- const out = /* @__PURE__ */ Object.create(null);
742
- for (const [selector, declarations] of Object.entries(pseudos)) {
743
- out[selector] = snapshotDeclarations(declarations);
744
- }
745
- return out;
746
- }
747
- function snapshotAtBlock(block) {
748
- if (!isPlainObject(block)) return block;
749
- const out = /* @__PURE__ */ Object.create(null);
750
- if ("declarations" in block) out.declarations = snapshotDeclarations(block.declarations);
751
- if ("pseudos" in block) out.pseudos = snapshotPseudos(block.pseudos);
752
- return out;
753
- }
754
- function snapshotAtBlocks(blocks) {
755
- if (!isPlainObject(blocks)) return blocks;
756
- const out = /* @__PURE__ */ Object.create(null);
757
- for (const [condition, block] of Object.entries(blocks)) {
758
- out[condition] = snapshotAtBlock(block);
759
- }
760
- return out;
761
- }
762
- function snapshotAtomDefinition(atom) {
763
- if (!isPlainObject(atom)) return atom;
764
- const out = /* @__PURE__ */ Object.create(null);
765
- if ("declarations" in atom) out.declarations = snapshotDeclarations(atom.declarations);
766
- if ("pseudos" in atom) out.pseudos = snapshotPseudos(atom.pseudos);
767
- if ("media" in atom) out.media = snapshotAtBlocks(atom.media);
768
- if ("container" in atom) out.container = snapshotAtBlocks(atom.container);
769
- return out;
770
- }
771
- function snapshotExtendMap(map) {
772
- const out = /* @__PURE__ */ Object.create(null);
773
- for (const [name, atom] of Object.entries(map)) {
774
- out[name] = snapshotAtomDefinition(atom);
775
- }
776
- return out;
777
- }
778
-
779
- // src/directive/fold.ts
780
- function splitAvailable(text) {
781
- const lines = text.split("\n");
782
- const last = lines.at(-1);
783
- if (lines.length > 1 && last?.startsWith("Available: ")) {
784
- return { body: lines.slice(0, -1).join("\n"), available: last };
785
- }
786
- return { body: text, available: void 0 };
787
- }
788
- function withoutNavePrefix(text) {
789
- return text.startsWith("@nave: ") ? text.slice(7) : text;
790
- }
791
- function foldLocated(entries) {
792
- const parts = entries.map((entry) => ({ ...splitAvailable(entry.text), entry }));
793
- const [first, ...rest] = parts;
794
- const lines = [first.body];
795
- if (rest.length > 0) {
796
- lines.push(`${rest.length} more in this stylesheet:`);
797
- for (const { body, entry } of rest) {
798
- lines.push(`${entry.line}:${entry.column}: ${withoutNavePrefix(body)}`);
799
- }
800
- }
801
- const available = parts.find((part) => part.available !== void 0)?.available;
802
- if (available) lines.push(available);
803
- return lines.join("\n");
804
- }
805
-
806
- // src/validate-extend-scan.ts
807
- function isInert(token) {
808
- return token.type === "whitespace-token" || token.type === "comment";
809
- }
810
- function isOnlyInert(tokens, from, to) {
811
- for (let i = from; i < to; i++) if (!isInert(tokens[i])) return false;
812
- return true;
813
- }
814
- function hasStrayCurlyCloser(wrapped, from, to) {
815
- const matchedClosers = new Set(wrapped.closerFor);
816
- for (let i = from; i < to; i++) {
817
- if (wrapped.tokens[i].type === "}-token" && !matchedClosers.has(i)) return true;
818
- }
819
- return false;
820
- }
821
- function hasPaddedComment(tokens, from, to) {
822
- for (let i = from; i < to; i++) {
823
- if (tokens[i].type !== "comment") continue;
824
- if (tokens[i - 1]?.type === "whitespace-token" || tokens[i + 1]?.type === "whitespace-token")
825
- return true;
826
- }
827
- return false;
828
- }
829
- function hasTopLevelComment(wrapped, from, to) {
830
- let i = from;
831
- while (i < to) {
832
- const closer = wrapped.closerFor[i];
833
- const type = wrapped.tokens[i].type;
834
- if (closer !== -1 && (type === "(-token" || type === "function-token")) {
835
- i = closer + 1;
836
- continue;
837
- }
838
- if (type === "comment") return true;
839
- i++;
840
- }
841
- return false;
842
- }
843
- function hasBrokenString(tokens, from, to) {
844
- for (let i = from; i < to; i++) {
845
- if (tokens[i].type === "bad-string-token" || tokens[i].type === "bad-url-token") return true;
846
- }
847
- return false;
848
- }
849
- function hasUrl(tokens, from, to) {
850
- for (let i = from; i < to; i++) {
851
- if (tokens[i].type === "url-token" && tokens[i].raw.includes("/*")) return true;
852
- }
853
- return false;
854
- }
855
- function lastNonWhitespace(tokens, from, to) {
856
- let i = to - 1;
857
- while (i >= from && tokens[i].type === "whitespace-token") i--;
858
- return i;
859
- }
860
- function hasTopLevelColon(wrapped, from, to) {
861
- let i = from;
862
- while (i < to) {
863
- const closer = wrapped.closerFor[i];
864
- const type = wrapped.tokens[i].type;
865
- if (closer !== -1 && (type === "(-token" || type === "function-token")) {
866
- i = closer + 1;
867
- continue;
868
- }
869
- if (wrapped.tokens[i].type === "colon-token") return true;
870
- i++;
871
- }
872
- return false;
873
- }
874
- function hasEscapedCommentOpener(text) {
875
- const outsideStrings = tokenize(text).map((token) => token.type === "string-token" ? '""' : token.raw).join("");
876
- return outsideStrings.includes(String.raw`\/*`);
877
- }
878
- function hasHtmlBreakout(text) {
879
- return /<\/?style\b/i.test(text) || text.includes("<!--");
880
- }
881
- function hasUnstrippedTrailingSpace(text) {
882
- const tokens = tokenize(text);
883
- const last = lastNonWhitespace(tokens, 0, tokens.length);
884
- const raw = tokens[last]?.raw;
885
- return raw !== void 0 && raw !== raw.trimEnd();
886
- }
887
- function hasUnstrippedLeadingSpace(text) {
888
- const tokens = tokenize(text);
889
- const first = tokens.findIndex((token) => token.type !== "whitespace-token");
890
- const raw = tokens[first]?.raw;
891
- return raw !== void 0 && raw !== raw.trimStart();
892
- }
893
- function hasEscapedWhitespace(text) {
894
- return /\\[\da-f]{0,6}[\t\n\f\r ]/i.test(text);
895
- }
896
-
897
- // src/validate-extend-breakout.ts
898
- var SENTINEL = ".nave-sentinel{}";
899
- var ATOM = "probe";
900
- function atomFor(splice) {
901
- if (splice.kind === "declarations") {
902
- return { atoms: { [ATOM]: { declarations: splice.declarations } }, nested: 0 };
903
- }
904
- const color = { color: "red" };
905
- if (splice.kind === "pseudo") {
906
- const pseudos = { [splice.key]: color };
907
- return { atoms: { [ATOM]: { declarations: {}, pseudos } }, nested: 1 };
908
- }
909
- const blocks = { [splice.condition]: { declarations: color } };
910
- return { atoms: { [ATOM]: { declarations: {}, [splice.atName]: blocks } }, nested: 1 };
911
- }
912
- function itemsIn(tokens, closerFor, from, to) {
913
- const items = [];
914
- let i = from;
915
- while (i < to) {
916
- const type = tokens[i].type;
917
- if (type === "whitespace-token" || type === "comment") {
918
- i++;
919
- continue;
920
- }
921
- const item = readItem(tokens, i, to, closerFor);
922
- items.push(item);
923
- i = Math.max(item.end, i + 1);
924
- }
925
- return items;
926
- }
927
- function textOf(tokens, from, to) {
928
- return tokens.slice(from, to).map((token) => token.raw).join("");
929
- }
930
- function isExpectedNested(splice, tokens, item) {
931
- if (splice.kind === "condition") {
932
- return item.kind === "at-rule" && item.atKeyword === splice.atName;
933
- }
934
- if (item.kind !== "rule" || item.blockStart === void 0) return false;
935
- return textOf(tokens, item.start, item.blockStart - 1).trim() === anchorSelectorList(splice.key);
936
- }
937
- function readRuleThenSentinel(css) {
938
- const tokens = tokenize(css);
939
- const closerFor = matchBrackets(tokens);
940
- const [rule, sentinel, ...rest] = itemsIn(tokens, closerFor, 0, tokens.length);
941
- if (rule === void 0 || sentinel === void 0 || rest.length > 0) return void 0;
942
- if (rule.blockStart === void 0 || closerFor[rule.blockStart - 1] !== rule.blockEnd) {
943
- return void 0;
944
- }
945
- const isSentinelWhereAppended = textOf(tokens, 0, sentinel.start).length === css.length - SENTINEL.length;
946
- const isSentinelText = textOf(tokens, sentinel.start, sentinel.end) === SENTINEL;
947
- const isClosedBeforeIt = isOnlyInert(tokens, rule.end, sentinel.start);
948
- return isSentinelWhereAppended && isSentinelText && isClosedBeforeIt ? { closerFor, rule, tokens } : void 0;
949
- }
950
- function isInsideRule(splice) {
951
- const { atoms: atoms2, nested } = atomFor(splice);
952
- let css;
953
- try {
954
- css = expandText(`.p { @nave ${ATOM}; }
955
- ${SENTINEL}`, {
956
- extend: atoms2,
957
- onUnknown: "ignore"
958
- }).css;
959
- } catch {
960
- return true;
961
- }
962
- const read = readRuleThenSentinel(css);
963
- if (read === void 0) return false;
964
- const { closerFor, rule, tokens } = read;
965
- const inner = itemsIn(tokens, closerFor, rule.blockStart, rule.blockEnd);
966
- const nestedItems = inner.filter((item) => item.kind === "rule" || item.kind === "at-rule");
967
- if (nestedItems.length !== nested) return false;
968
- return splice.kind === "declarations" || isExpectedNested(splice, tokens, nestedItems[0]);
969
- }
970
-
971
- // src/validate-extend-walk.ts
972
- function refuse(describedAs, clause) {
973
- throw new Error(
974
- `navePlugin({ extend }): ${describedAs} ${clause} extend is trusted, consumer-authored code, not sanitised input \u2014 fix the atom definition.`
975
- );
976
- }
977
- var ESCAPED_LESS_THAN = String.raw`\3c`;
978
- function fail(describedAs, reason, value, splice) {
979
- if (reason === "rewritten") {
980
- refuse(
981
- describedAs,
982
- `parses, but would not be written back exactly as given: ${JSON.stringify(value)}. A "<" before "style", "/style" or "!--" is written back escaped: write the "<" as ${ESCAPED_LESS_THAN} with a space after it (${ESCAPED_LESS_THAN} /style), or as %3C inside a URL. Whitespace at either end of it is not written back as given: remove it.`
983
- );
984
- }
985
- const clause = "does not parse as a single CSS declaration, selector or condition, ";
986
- if (isInsideRule(splice)) {
987
- refuse(
988
- describedAs,
989
- `${clause}though it would stay inside the rule it is spliced into: ${JSON.stringify(value)}. Write exactly one, with nothing else in it: no ";" or second declaration, no comment, and no trailing backslash.`
990
- );
991
- }
992
- refuse(
993
- describedAs,
994
- `${clause}and would break out of the rule it is spliced into: ${JSON.stringify(value)}.`
995
- );
996
- }
997
- function kindOf(value) {
998
- if (Array.isArray(value)) return "an array";
999
- const boxed = boxedPrimitiveKind(value);
1000
- return boxed === void 0 ? `a ${typeof value}` : `a ${boxed} object`;
1001
- }
1002
- function quoted(value) {
1003
- if (typeof value === "function" || typeof value === "symbol") return void 0;
1004
- if (typeof value === "bigint") return `${value}n`;
1005
- if (typeof value === "number") return String(value);
1006
- try {
1007
- return JSON.stringify(value);
1008
- } catch {
1009
- return void 0;
1010
- }
1011
- }
1012
- function printable(value) {
1013
- const isBoxed = boxedPrimitiveKind(value) !== void 0;
1014
- return quoted(isBoxed ? unboxedPrimitive(value) : value);
1015
- }
1016
- function failShape(describedAs, value, isAtom) {
1017
- const printed = printable(value);
1018
- const shown = printed === void 0 ? "" : `: ${printed}`;
1019
- const advice = isAtom ? "Write an atom as { declarations: { ... } }." : "Write it as a plain object ({ ... }).";
1020
- refuse(describedAs, `is ${kindOf(value)}, not a plain object${shown}. ${advice}`);
1021
- }
1022
- function assertShape(value, describedAs) {
1023
- if (!value || isPlainObject(value)) return;
1024
- failShape(describedAs, value, false);
1025
- }
1026
- function assertDeclarationsSafe(declarations, where, p) {
1027
- assertShape(declarations, `${where}'s declarations`);
1028
- if (!isPlainObject(declarations)) return;
1029
- for (const [prop, value] of Object.entries(declarations)) {
1030
- const propRefusal = p.propRefusal(prop);
1031
- if (propRefusal) {
1032
- fail(`${where}'s declaration property "${prop}"`, propRefusal, prop, {
1033
- declarations: { [prop]: "0" },
1034
- kind: "declarations"
1035
- });
1036
- }
1037
- if (typeof value !== "string") continue;
1038
- const valueRefusal = p.declarationRefusal(prop, value);
1039
- if (valueRefusal) {
1040
- fail(`${where}'s declaration value for "${prop}"`, valueRefusal, value, {
1041
- declarations: { [prop]: value },
1042
- kind: "declarations"
1043
- });
1044
- }
1045
- }
1046
- }
1047
- function assertPseudosSafe(pseudos, where, p) {
1048
- assertShape(pseudos, `${where}'s pseudos`);
1049
- if (!isPlainObject(pseudos)) return;
1050
- for (const [pseudo, declarations] of Object.entries(pseudos)) {
1051
- const refusal = p.selectorRefusal(pseudo);
1052
- if (refusal) {
1053
- fail(`${where}'s pseudo "${pseudo}"`, refusal, pseudo, { key: pseudo, kind: "pseudo" });
1054
- }
1055
- assertDeclarationsSafe(declarations, `${where}'s pseudo "${pseudo}"`, p);
1056
- }
1057
- }
1058
- function assertAtBlocksSafe(blocks, atName, where, p) {
1059
- assertShape(blocks, `${where}'s ${atName} map`);
1060
- if (!isPlainObject(blocks)) return;
1061
- for (const [condition, block] of Object.entries(blocks)) {
1062
- const refusal = p.conditionRefusal(atName, condition);
1063
- if (refusal) {
1064
- fail(`${where}'s ${atName} condition "${condition}"`, refusal, condition, {
1065
- atName,
1066
- condition,
1067
- kind: "condition"
1068
- });
1069
- }
1070
- const blockWhere = `${where}'s ${atName} "${condition}"`;
1071
- assertShape(block, `${blockWhere} block`);
1072
- if (!isPlainObject(block)) continue;
1073
- assertDeclarationsSafe(block.declarations, blockWhere, p);
1074
- assertPseudosSafe(block.pseudos, blockWhere, p);
1075
- }
1076
- }
1077
- function walkExtendAtoms(extend, predicates) {
1078
- for (const [name, atom] of Object.entries(extend)) {
1079
- if (!atom) continue;
1080
- const where = `atom "${name}"`;
1081
- if (!isPlainObject(atom)) failShape(where, atom, true);
1082
- assertDeclarationsSafe(atom.declarations, where, predicates);
1083
- assertPseudosSafe(atom.pseudos, where, predicates);
1084
- assertAtBlocksSafe(atom.media, "media", where, predicates);
1085
- assertAtBlocksSafe(atom.container, "container", where, predicates);
1086
- }
1087
- }
1088
-
1089
- export { WORKAROUND_GROUP_AT_RULE_NAMES, anchorSelectorList, expandText, foldLocated, hasBrokenString, hasEscapedCommentOpener, hasEscapedWhitespace, hasHtmlBreakout, hasPaddedComment, hasStrayCurlyCloser, hasTopLevelColon, hasTopLevelComment, hasUnstrippedLeadingSpace, hasUnstrippedTrailingSpace, hasUrl, isInert, isOnlyInert, lastNonWhitespace, plan, snapshotExtendMap, walkExtendAtoms };