@navecss/core 0.1.1 → 0.3.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/postcss.js CHANGED
@@ -1,138 +1,39 @@
1
- import { atoms } from './chunk-BVON7XKC.js';
1
+ import { resolveExtendSpecifier, applyExtendModule } from './chunk-NKUZNRF4.js';
2
+ import { snapshotExtendMap, walkExtendAtoms, foldLocated, anchorSelectorList, plan, formatDiagnostic, WORKAROUND_GROUP_AT_RULE_NAMES } from './chunk-S7DZUVV3.js';
3
+ import { tokenize } from './chunk-NFS2REPQ.js';
4
+ import './chunk-LIUB4EES.js';
2
5
  import postcss from 'postcss';
3
6
 
4
- // src/selector-utils.ts
5
- function nextQuote(quote, char) {
6
- if (quote) return char === quote ? void 0 : quote;
7
- return char === '"' || char === "'" ? char : quote;
8
- }
9
- function depthDelta(quote, char) {
10
- if (quote) return 0;
11
- if (char === "(" || char === "[") return 1;
12
- if (char === ")" || char === "]") return -1;
13
- return 0;
14
- }
15
- function consumeSplitChar(state, char) {
16
- const { branches, depth, quote, isEscaped, current } = state;
17
- if (isEscaped) {
18
- return { branches, depth, quote, isEscaped: false, current: current + char };
19
- }
20
- if (char === "\\") {
21
- return { branches, depth, quote, isEscaped: true, current: current + char };
22
- }
23
- const nextDepth = depth + depthDelta(quote, char);
24
- if (!quote && char === "," && nextDepth === 0) {
25
- return {
26
- branches: [...branches, current],
27
- depth: nextDepth,
28
- quote,
29
- isEscaped: false,
30
- current: ""
31
- };
32
- }
33
- return {
34
- branches,
35
- depth: nextDepth,
36
- quote: nextQuote(quote, char),
37
- isEscaped: false,
38
- current: current + char
39
- };
40
- }
41
- function splitTopLevel(selectorList) {
42
- let state = {
43
- branches: [],
44
- depth: 0,
45
- quote: void 0,
46
- isEscaped: false,
47
- current: ""
48
- };
49
- for (const char of selectorList) state = consumeSplitChar(state, char);
50
- return [...state.branches, state.current];
51
- }
52
- function hasUnquotedAmpersand(branch) {
53
- let quote;
54
- let isEscaped = false;
55
- for (const char of branch) {
56
- if (isEscaped) {
57
- isEscaped = false;
58
- continue;
59
- }
60
- if (char === "\\") {
61
- isEscaped = true;
62
- continue;
63
- }
64
- if (!quote && char === "&") return true;
65
- quote = nextQuote(quote, char);
66
- }
67
- return false;
68
- }
69
- function anchorSelectorList(selectorList) {
70
- return splitTopLevel(selectorList).map((branch) => {
71
- const trimmed = branch.trim();
72
- if (trimmed === "") {
73
- throw new Error(
74
- `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.`
75
- );
76
- }
77
- return hasUnquotedAmpersand(trimmed) ? trimmed : `&${trimmed}`;
78
- }).join(", ");
79
- }
80
-
81
7
  // src/postcss-nested-builders.ts
82
- var DIRECTIVE = "nave";
83
- function buildNestedRule(selector, decls) {
84
- const rule = postcss.rule({ selector });
85
- for (const [prop, value] of Object.entries(decls)) {
86
- rule.append(postcss.decl({ prop, value }));
87
- }
88
- return rule;
89
- }
90
- function buildPseudoRules(pseudos) {
91
- return Object.entries(pseudos).map(
92
- ([pseudo, decls]) => buildNestedRule(anchorSelectorList(pseudo), decls)
93
- );
94
- }
95
- function buildInnerRules(block) {
96
- const rules = [];
97
- if (block.declarations) rules.push(buildNestedRule("&", block.declarations));
98
- if (block.pseudos) rules.push(...buildPseudoRules(block.pseudos));
99
- return rules;
100
- }
101
- function buildAtBlock(atName, condition, block) {
102
- const inner = buildInnerRules(block);
103
- if (inner.length === 0) return void 0;
104
- const node = postcss.atRule({ name: atName, params: condition });
105
- for (const rule of inner) node.append(rule);
106
- return node;
107
- }
108
- function buildNested(atom) {
109
- const nodes = [];
110
- if (atom.pseudos) nodes.push(...buildPseudoRules(atom.pseudos));
111
- for (const atName of ["media", "container"]) {
112
- const blocks = atom[atName] ?? {};
113
- for (const [condition, block] of Object.entries(blocks)) {
114
- const node = buildAtBlock(atName, condition, block);
115
- if (node) nodes.push(node);
116
- }
117
- }
118
- return nodes;
119
- }
120
8
  function isNestedNode(node) {
121
- return node.type === "rule" || node.type === "atrule" && node.name !== DIRECTIVE;
9
+ return node.type === "rule" || node.type === "atrule" && node.name.toLowerCase() !== "nave";
122
10
  }
123
11
  function isFollowingNestedNode(rule, atRule) {
124
12
  return rule.nodes.slice(0, rule.index(atRule)).some((node) => isNestedNode(node));
125
13
  }
126
14
 
127
15
  // src/postcss-node-utils.ts
128
- function isInsideKeyframes(rule) {
129
- let node = rule.parent;
16
+ function isInsideKeyframes(container) {
17
+ let node = container;
130
18
  while (node) {
131
19
  if (node.type === "atrule" && /keyframes$/i.test(node.name)) return true;
132
20
  node = node.parent;
133
21
  }
134
22
  return false;
135
23
  }
24
+ function hasWorkaroundSentence(refusedParent) {
25
+ if (refusedParent.type !== "atrule") return false;
26
+ if (!WORKAROUND_GROUP_AT_RULE_NAMES.has(refusedParent.name.toLowerCase())) {
27
+ return false;
28
+ }
29
+ if (isInsideKeyframes(refusedParent)) return false;
30
+ let current = refusedParent.parent;
31
+ while (current) {
32
+ if (current.type === "rule") return true;
33
+ current = current.parent;
34
+ }
35
+ return false;
36
+ }
136
37
  function stampSource(node, source) {
137
38
  if (!source) return;
138
39
  node.source = source;
@@ -141,88 +42,265 @@ function stampSource(node, source) {
141
42
  });
142
43
  }
143
44
 
144
- // src/postcss.ts
145
- function reportUnknown(msg, ctx) {
146
- const { atRule, onUnknown, result } = ctx;
147
- if (onUnknown === "warn") {
148
- atRule.warn(result, msg);
149
- return;
150
- }
151
- if (onUnknown === "ignore") return;
152
- throw atRule.error(msg);
45
+ // src/postcss-at-rule.ts
46
+ function currentParams(atRule) {
47
+ const raw = atRule.raws.params;
48
+ if (raw?.value === atRule.params) return raw.raw;
49
+ return atRule.params;
50
+ }
51
+ function matchNaveAtRule(atRule) {
52
+ const afterName = atRule.raws.afterName ?? " ";
53
+ const paramsRaw = currentParams(atRule);
54
+ const nameBoundary = 1 + atRule.name.length;
55
+ const full = `@${atRule.name}${afterName}${paramsRaw}`;
56
+ const token = tokenize(full)[0];
57
+ const commonPrelude = { prelude: paramsRaw, preludeStartIndex: nameBoundary + afterName.length };
58
+ if (token?.type !== "at-keyword-token") return { isNave: false, ...commonPrelude };
59
+ const isNave = (token.structured?.value).toLowerCase() === "nave";
60
+ if (token.endIndex === nameBoundary) return { isNave, ...commonPrelude };
61
+ return { isNave, prelude: full.slice(token.endIndex), preludeStartIndex: token.endIndex };
153
62
  }
154
- function checkUnknownAtoms(names, validAtomNames, ctx) {
155
- if (names.length === 0) {
156
- reportUnknown("@nave: directive names no atom", ctx);
63
+ function atRuleErrorIndex(preludeStartIndex, diagnostic) {
64
+ if (diagnostic.code !== "unknown-atom" && diagnostic.code !== "bad-token") return void 0;
65
+ return preludeStartIndex + diagnostic.offset;
66
+ }
67
+ function reportDiagnostic(diagnostic, ctx, preludeStartIndex) {
68
+ const refusedParent = ctx.atRule.parent;
69
+ const isNestedGroup = diagnostic.code === "bad-parent" && refusedParent !== void 0 && hasWorkaroundSentence(refusedParent);
70
+ const text = formatDiagnostic(
71
+ isNestedGroup ? { ...diagnostic, detail: "nested-group" } : diagnostic,
72
+ {
73
+ extend: ctx.extend
74
+ }
75
+ );
76
+ const index = atRuleErrorIndex(preludeStartIndex, diagnostic);
77
+ if (ctx.onUnknown === "warn") {
78
+ ctx.atRule.warn(ctx.result, text, index === void 0 ? {} : { index });
157
79
  return;
158
80
  }
159
- for (const name of names) {
160
- if (validAtomNames.has(name)) continue;
161
- reportUnknown(
162
- `@nave: unknown atom "${name}". Available: ${[...validAtomNames].join(", ")}`,
163
- ctx
164
- );
165
- }
81
+ if (ctx.onUnknown === "ignore") return;
82
+ ctx.fold.push({ atRule: ctx.atRule, text, index });
166
83
  }
167
- function insertDeclarations(atRule, declNodes, isFollowingNested) {
168
- if (declNodes.length === 0) return;
169
- if (!isFollowingNested) {
170
- for (const decl of declNodes) atRule.before(decl);
84
+ function declNode(atRule, decl) {
85
+ const node = postcss.decl({ prop: decl.prop, value: decl.value });
86
+ if (atRule.source) node.source = atRule.source;
87
+ return node;
88
+ }
89
+ function insertDeclarations(atRule, declarations, isWrapped) {
90
+ if (declarations.length === 0) return;
91
+ if (!isWrapped) {
92
+ for (const decl of declarations) atRule.before(declNode(atRule, decl));
171
93
  return;
172
94
  }
173
95
  const wrapper = postcss.rule({ selector: "&" });
174
96
  if (atRule.source) wrapper.source = atRule.source;
175
- for (const decl of declNodes) wrapper.append(decl);
97
+ for (const decl of declarations) wrapper.append(declNode(atRule, decl));
176
98
  atRule.before(wrapper);
177
99
  }
178
- var navePlugin = (options = {}) => {
179
- const { onUnknown = "error", extend = {} } = options;
180
- const allAtoms = { ...atoms, ...extend };
181
- const validAtomNames = new Set(
182
- Object.entries(allAtoms).filter(([, definition]) => definition).map(([name]) => name)
100
+ function buildRule(selector, declarations) {
101
+ const rule = postcss.rule({ selector });
102
+ for (const decl of declarations) rule.append(postcss.decl({ prop: decl.prop, value: decl.value }));
103
+ return rule;
104
+ }
105
+ function buildAppendedNode(block) {
106
+ if (block.kind === "pseudo") return buildRule(block.selector, block.declarations);
107
+ const node = postcss.atRule({ name: block.kind, params: block.condition });
108
+ if (block.declarations.length > 0) node.append(buildRule("&", block.declarations));
109
+ for (const pseudo of block.pseudos) node.append(buildRule(pseudo.selector, pseudo.declarations));
110
+ return node;
111
+ }
112
+ function throwMalformedAtom(atRule, error) {
113
+ const message = error instanceof Error ? error.message : String(error);
114
+ throw atRule.error(message);
115
+ }
116
+ function planAtRule(atRule, extend, prelude) {
117
+ const parent = atRule.parent;
118
+ const isStyleRuleParent = parent?.type === "rule";
119
+ const isFollowingNestedNodeHere = isStyleRuleParent ? isFollowingNestedNode(parent, atRule) : false;
120
+ try {
121
+ return {
122
+ isStyleRuleParent,
123
+ parent,
124
+ result: plan(
125
+ prelude,
126
+ {
127
+ isStyleRuleParent,
128
+ isInsideKeyframes: parent !== void 0 && isInsideKeyframes(parent),
129
+ isFollowingNestedNode: isFollowingNestedNodeHere
130
+ },
131
+ { extend }
132
+ )
133
+ };
134
+ } catch (error) {
135
+ return throwMalformedAtom(atRule, error);
136
+ }
137
+ }
138
+ function hasBlock(atRule) {
139
+ return atRule.nodes !== void 0;
140
+ }
141
+ function handleAtRule(atRule, ctx) {
142
+ const match = matchNaveAtRule(atRule);
143
+ if (!match.isNave) return;
144
+ const fullCtx = { ...ctx, atRule };
145
+ const {
146
+ isStyleRuleParent,
147
+ parent,
148
+ result: planResult
149
+ } = planAtRule(atRule, ctx.extend, match.prelude);
150
+ for (const diagnostic of planResult.diagnostics) {
151
+ reportDiagnostic(diagnostic, fullCtx, match.preludeStartIndex);
152
+ }
153
+ if (hasBlock(atRule)) {
154
+ reportDiagnostic(
155
+ { code: "has-block", offset: 0, endOffset: atRule.toString().length },
156
+ fullCtx,
157
+ match.preludeStartIndex
158
+ );
159
+ }
160
+ insertDeclarations(atRule, planResult.declarations, planResult.wrapInAmpersand);
161
+ atRule.remove();
162
+ if (isStyleRuleParent && parent) {
163
+ for (const block of planResult.blocks) {
164
+ const node = buildAppendedNode(block);
165
+ stampSource(node, atRule.source);
166
+ parent.append(node);
167
+ }
168
+ }
169
+ }
170
+
171
+ // src/postcss-fold.ts
172
+ function positionOf(entry, fallbackLine) {
173
+ if (!entry.atRule.source) return { column: 0, line: fallbackLine };
174
+ return entry.atRule.positionBy(entry.index === void 0 ? {} : { index: entry.index });
175
+ }
176
+ function sortFoldBySourceOrder(entries) {
177
+ return entries.map((entry, originalIndex) => ({ entry, originalIndex })).toSorted((a, b) => {
178
+ const pa = positionOf(a.entry, a.originalIndex);
179
+ const pb = positionOf(b.entry, b.originalIndex);
180
+ return pa.line - pb.line || pa.column - pb.column;
181
+ }).map(({ entry }) => entry);
182
+ }
183
+ function foldMessage(entries) {
184
+ return foldLocated(
185
+ entries.map((entry, i) => {
186
+ const position = positionOf(entry, i);
187
+ return { text: entry.text, line: position.line, column: position.column };
188
+ })
183
189
  );
190
+ }
191
+ function unescapeHtml(text) {
192
+ return text.replaceAll(/\\3c (?=\/?style\b|!--)/gi, "<");
193
+ }
194
+ function compact(text) {
195
+ return text.replaceAll(/\s+/g, "");
196
+ }
197
+ function declarationRefusal(prop, value) {
198
+ let root;
199
+ try {
200
+ root = postcss.parse(`a{${prop}:${value}}`);
201
+ } catch {
202
+ return "break-out";
203
+ }
204
+ if (root.nodes.length !== 1) return "break-out";
205
+ const rule = root.nodes[0];
206
+ if (rule.type !== "rule" || rule.nodes.length !== 1) return "break-out";
207
+ const decl = rule.nodes[0];
208
+ if (decl.type !== "decl" || decl.prop !== prop) return "break-out";
209
+ const given = `${prop}:${value}`;
210
+ const written = decl.toString();
211
+ if (written === given.trimEnd()) return void 0;
212
+ return compact(unescapeHtml(written)) === compact(given) ? "rewritten" : "break-out";
213
+ }
214
+ function propRefusal(prop) {
215
+ return declarationRefusal(prop, "0");
216
+ }
217
+ function selectorRefusal(key) {
218
+ let selector;
219
+ try {
220
+ selector = anchorSelectorList(key);
221
+ } catch {
222
+ return "break-out";
223
+ }
224
+ let root;
225
+ try {
226
+ root = postcss.parse(`${selector}{}`);
227
+ } catch {
228
+ return "break-out";
229
+ }
230
+ if (root.nodes.length !== 1) return "break-out";
231
+ const rule = root.nodes[0];
232
+ const isOneEmptyRule = rule.type === "rule" && rule.selector === selector;
233
+ return isOneEmptyRule && rule.nodes.length === 0 ? void 0 : "break-out";
234
+ }
235
+ function conditionRefusal(atName, condition) {
236
+ let root;
237
+ try {
238
+ root = postcss.parse(`@${atName} ${condition}{}`);
239
+ } catch {
240
+ return "break-out";
241
+ }
242
+ if (root.nodes.length !== 1) return "break-out";
243
+ const node = root.nodes[0];
244
+ if (node.type !== "atrule" || node.name !== atName || (node.nodes?.length ?? 0) !== 0) {
245
+ return "break-out";
246
+ }
247
+ if (node.params === condition.trim()) return void 0;
248
+ return compact(node.params) === compact(condition) ? "rewritten" : "break-out";
249
+ }
250
+ function validateExtendAtoms(extend) {
251
+ walkExtendAtoms(extend, { declarationRefusal, propRefusal, selectorRefusal, conditionRefusal });
252
+ }
253
+
254
+ // src/postcss.ts
255
+ var navePlugin = (options = {}) => {
256
+ const { onUnknown = "error", extend: extendOption } = options;
257
+ const extendFile = typeof extendOption === "string" ? resolveExtendSpecifier(extendOption) : void 0;
258
+ const staticExtend = typeof extendOption === "object" ? snapshotExtendMap(extendOption) : {};
259
+ validateExtendAtoms(staticExtend);
260
+ const loadCache = /* @__PURE__ */ new Map();
184
261
  return {
185
262
  postcssPlugin: "postcss-nave",
186
- AtRule(atRule, { result }) {
187
- if (atRule.name !== DIRECTIVE) return;
188
- const ctx = { atRule, onUnknown, result };
189
- const parent = atRule.parent;
190
- if (parent?.type !== "rule") {
191
- reportUnknown("@nave must be the direct child of a CSS rule selector block", ctx);
192
- atRule.remove();
193
- return;
194
- }
195
- const rule = parent;
196
- if (isInsideKeyframes(rule)) {
197
- reportUnknown("@nave cannot be used inside @keyframes", ctx);
198
- atRule.remove();
199
- return;
200
- }
201
- const names = atRule.params.trim().split(/\s+/).filter(Boolean);
202
- checkUnknownAtoms(names, validAtomNames, ctx);
203
- const declNodes = [];
204
- const nested = names.flatMap((name) => {
205
- const atom = Object.hasOwn(allAtoms, name) ? allAtoms[name] : void 0;
206
- if (!atom) return [];
207
- if (typeof atom.declarations !== "object" || atom.declarations === null || Array.isArray(atom.declarations)) {
208
- throw atRule.error(`@nave: atom "${name}" is registered without a declarations object`);
209
- }
210
- for (const [prop, value] of Object.entries(atom.declarations)) {
211
- const decl = postcss.decl({ prop, value });
212
- if (atRule.source) decl.source = atRule.source;
213
- declNodes.push(decl);
263
+ // Per-run state lives here, not in the factory closure above: PostCSS
264
+ // calls `prepare(result)` once per `Result`, so two stylesheets sharing
265
+ // one `navePlugin()` instance never share a `fold` or a resolved
266
+ // `extend`, however their async hooks interleave (otherwise one
267
+ // stylesheet's problems, or its loaded `extend`, could leak into another's).
268
+ prepare(result) {
269
+ const fold = [];
270
+ let extend = staticExtend;
271
+ return {
272
+ Once() {
273
+ fold.length = 0;
274
+ if (extendFile === void 0) return;
275
+ result.messages.push({
276
+ type: "dependency",
277
+ plugin: "postcss-nave",
278
+ file: extendFile,
279
+ parent: result.opts.from
280
+ });
281
+ return applyExtendModule(extendFile, loadCache, (value) => {
282
+ const snapshot = snapshotExtendMap(value);
283
+ validateExtendAtoms(snapshot);
284
+ extend = snapshot;
285
+ });
286
+ },
287
+ AtRule(atRule) {
288
+ handleAtRule(atRule, { onUnknown, result, extend, fold });
289
+ },
290
+ OnceExit() {
291
+ if (fold.length === 0) return;
292
+ const ordered = sortFoldBySourceOrder(fold);
293
+ const first = ordered[0];
294
+ throw first.atRule.error(
295
+ foldMessage(ordered),
296
+ first.index === void 0 ? {} : { index: first.index }
297
+ );
214
298
  }
215
- return buildNested(atom);
216
- });
217
- insertDeclarations(atRule, declNodes, isFollowingNestedNode(rule, atRule));
218
- atRule.remove();
219
- for (const node of nested) {
220
- stampSource(node, atRule.source);
221
- rule.append(node);
222
- }
299
+ };
223
300
  }
224
301
  };
225
302
  };
226
303
  navePlugin.postcss = true;
304
+ var postcss_default = navePlugin;
227
305
 
228
- export { navePlugin };
306
+ export { postcss_default as default, navePlugin };