@jesscss/jess-parser 2.0.0-alpha.11 → 2.0.0-alpha.13
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/README.md +59 -8
- package/lib/chunks/parse-with.cjs +193 -0
- package/lib/chunks/parse-with.js +182 -0
- package/lib/chunks/trivia-labels.cjs +18 -0
- package/lib/chunks/trivia-labels.js +13 -0
- package/lib/cst/positions.cjs +24 -0
- package/lib/cst/positions.js +21 -0
- package/lib/cst.cjs +12 -5
- package/lib/cst.d.ts +9 -3
- package/lib/cst.js +11 -5
- package/lib/grammar/ast/positions.cjs +22009 -0
- package/lib/grammar/ast/positions.js +21983 -0
- package/lib/grammar/ast.cjs +22060 -0
- package/lib/grammar/ast.d.ts +11 -0
- package/lib/grammar/ast.js +22034 -0
- package/lib/grammar/cst/positions.cjs +22009 -0
- package/lib/grammar/cst/positions.js +21983 -0
- package/lib/grammar/cst.cjs +22060 -0
- package/lib/grammar/cst.d.ts +2 -0
- package/lib/grammar/cst.js +22034 -0
- package/lib/grammar-helpers.d.ts +208 -0
- package/lib/grammar.d.ts +4 -384
- package/lib/index.cjs +12 -36
- package/lib/index.d.ts +12 -12
- package/lib/index.js +12 -33
- package/lib/parse-error.d.ts +27 -0
- package/lib/parse-with.d.ts +14 -0
- package/lib/positions.cjs +11 -0
- package/lib/positions.js +9 -0
- package/lib/trivia-labels.d.ts +10 -0
- package/package.json +41 -11
- package/lib/grammar.cjs +0 -99862
- package/lib/grammar.js +0 -99858
package/README.md
CHANGED
|
@@ -62,10 +62,48 @@ Pass a different `startRule` (any capitalized grammar rule) to parse a fragment.
|
|
|
62
62
|
| Entry | Export | Purpose |
|
|
63
63
|
| --- | --- | --- |
|
|
64
64
|
| `@jesscss/jess-parser` (`.`) | `parse`, `JessParseError` | Parse Jess directly to canonical AST v2 `Stylesheet`; malformed input throws `JessParseError` with an offset and expected facts. |
|
|
65
|
-
| `@jesscss/jess-parser` (`.`) | `parseJessCst`, `jessGrammar` | Convenience exports for the public CST parser and canonical grammar artifact. |
|
|
66
65
|
| `@jesscss/jess-parser` (`.`) | `JessCstNode`, `JessCstLeaf`, `JessCstError`, `JessCstChild`, `JessCstParseResult`, `JessCstType` (types) | CST type definitions (aliases of the shared `@jesscss/css-parser/cst` types). |
|
|
67
|
-
| `@jesscss/jess-parser/cst` | `parseJessCst`, CST types |
|
|
68
|
-
| `@jesscss/jess-parser/grammar` | `
|
|
66
|
+
| `@jesscss/jess-parser/cst` | `parseJessCst`, `parseJessDoc`, CST types | Core-free parse of a Jess string to a CST. Compiled grammars are not re-exported from `.` — reach for a `/grammar` subpath so the main entry never loads a grammar build you did not ask for. |
|
|
67
|
+
| `@jesscss/jess-parser/grammar` | `jessGrammar` | The compiled Jess AST grammar (a rule map). Extend it with `compose()` or drive it directly with parseman's `run`. See the variant table below. |
|
|
68
|
+
|
|
69
|
+
### Line-aware entries
|
|
70
|
+
|
|
71
|
+
`parse` and the CST parsers come in two bindings, one per compiled table, so an
|
|
72
|
+
entry never loads a table it does not parse with:
|
|
73
|
+
|
|
74
|
+
| Entry | Export | Tree | Positions |
|
|
75
|
+
| --- | --- | --- | --- |
|
|
76
|
+
| `@jesscss/jess-parser` (`.`) | `parse` | AST | no |
|
|
77
|
+
| `@jesscss/jess-parser/positions` | `parse` | AST | yes |
|
|
78
|
+
| `@jesscss/jess-parser/cst` | `parseJessCst`, `parseJessDoc` | CST | no |
|
|
79
|
+
| `@jesscss/jess-parser/cst/positions` | `parseJessCst`, `parseJessDoc` | CST | yes |
|
|
80
|
+
|
|
81
|
+
The `/positions` entries export the same names bound to the line-aware table:
|
|
82
|
+
switching is a change of import specifier, not of call site.
|
|
83
|
+
|
|
84
|
+
### Choosing a grammar build
|
|
85
|
+
|
|
86
|
+
Each compiled grammar is a standalone multi-megabyte artifact, so the four
|
|
87
|
+
variants ship as four separate files. Importing one never loads the others.
|
|
88
|
+
Pick by the two questions the subpath name answers — which tree, and whether
|
|
89
|
+
source positions are tracked:
|
|
90
|
+
|
|
91
|
+
| Subpath | Export | Tree | Positions |
|
|
92
|
+
| --- | --- | --- | --- |
|
|
93
|
+
| `@jesscss/jess-parser/grammar/ast` | `jessGrammar` | AST | no |
|
|
94
|
+
| `@jesscss/jess-parser/grammar/ast/positions` | `jessPositionsGrammar` | AST | yes |
|
|
95
|
+
| `@jesscss/jess-parser/grammar/cst` | `jessCstGrammar` | CST | no |
|
|
96
|
+
| `@jesscss/jess-parser/grammar/cst/positions` | `jessCstPositionsGrammar` | CST | yes |
|
|
97
|
+
|
|
98
|
+
`@jesscss/jess-parser/grammar` is an alias for `/grammar/ast`, the build the
|
|
99
|
+
shipping `parse()` route uses. It is not a barrel: it exposes the AST variant
|
|
100
|
+
only, so importing it cannot pull the other three in.
|
|
101
|
+
|
|
102
|
+
The positions variants set `startLine`/`startColumn` on every span. There is no
|
|
103
|
+
`trackLines` option: an option would force one module to name both tables, and
|
|
104
|
+
Node executes every module it statically imports, so the choice is which entry
|
|
105
|
+
you import. Error tolerance is not a property of a build — the CST runner
|
|
106
|
+
collects `result.errors` on either CST variant.
|
|
69
107
|
|
|
70
108
|
## Default CST shape
|
|
71
109
|
|
|
@@ -83,19 +121,32 @@ Parsing `$brand: #3366ff;` yields:
|
|
|
83
121
|
{
|
|
84
122
|
"_tag": "node", "type": "StyleSheet", "grammarType": "Stylesheet", "span": { "start": 0, "end": 16 },
|
|
85
123
|
"children": [
|
|
86
|
-
{ "_tag": "node", "type": "
|
|
124
|
+
{ "_tag": "node", "type": "VariableDeclaration", "grammarType": "VariableDeclaration", "span": { "start": 0, "end": 16 },
|
|
87
125
|
"children": [
|
|
88
|
-
{ "_tag": "leaf", "value": "$
|
|
126
|
+
{ "_tag": "leaf", "value": "$", "span": { "start": 0, "end": 1 } },
|
|
127
|
+
{ "_tag": "leaf", "value": "brand", "span": { "start": 1, "end": 6 } },
|
|
89
128
|
{ "_tag": "leaf", "value": ":", "span": { "start": 6, "end": 7 } },
|
|
90
|
-
{ "_tag": "node", "type": "
|
|
91
|
-
"children": [
|
|
129
|
+
{ "_tag": "node", "type": "Value", "grammarType": "Value", "span": { "start": 8, "end": 15 },
|
|
130
|
+
"children": [
|
|
131
|
+
{ "_tag": "node", "type": "ValueTerm", "grammarType": "ValueTerm", "span": { "start": 8, "end": 15 },
|
|
132
|
+
"children": [
|
|
133
|
+
{ "_tag": "node", "type": "ValueSpaceGroup", "grammarType": "ValueSpaceGroup", "span": { "start": 8, "end": 15 },
|
|
134
|
+
"children": [
|
|
135
|
+
{ "_tag": "node", "type": "ValueAtom", "grammarType": "ValueAtom", "span": { "start": 8, "end": 15 },
|
|
136
|
+
"children": [
|
|
137
|
+
{ "_tag": "node", "type": "Color", "grammarType": "Color", "span": { "start": 8, "end": 15 },
|
|
138
|
+
"children": [ { "_tag": "leaf", "value": "#3366ff", "span": { "start": 8, "end": 15 } } ] }
|
|
139
|
+
] }
|
|
140
|
+
] }
|
|
141
|
+
] }
|
|
142
|
+
] },
|
|
92
143
|
{ "_tag": "leaf", "value": ";", "span": { "start": 15, "end": 16 } }
|
|
93
144
|
] }
|
|
94
145
|
]
|
|
95
146
|
}
|
|
96
147
|
```
|
|
97
148
|
|
|
98
|
-
Jess-specific grammar rules include `
|
|
149
|
+
Jess-specific grammar rules include `VariableDeclaration` (`$x: …`), `VariableReference` / `ReferenceCall` (`$x`, `$x.prop`, `$x[0]`, callable chains), `Expression*` (inside `$(…)`), `MixinDefinition`, `MixinCall`, `InterpolatedSimple` (`.widget-${side}`), and the `@-compose`/`@-export`/`@-from`/`@-use` import at-rules.
|
|
99
150
|
|
|
100
151
|
Pass `{ collapse: true }` to request parseman's transparent-wrapper collapse while preserving source leaves and grammar ownership.
|
|
101
152
|
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
const require_trivia_labels = require("./trivia-labels.cjs");
|
|
2
|
+
let parseman = require("parseman");
|
|
3
|
+
let _jesscss_core_ast = require("@jesscss/core/ast");
|
|
4
|
+
//#region src/parse-error.ts
|
|
5
|
+
/**
|
|
6
|
+
* The public Jess parse failure lives in its own module so that both AST
|
|
7
|
+
* entries — `.` and `./positions` — can export it without either one reaching
|
|
8
|
+
* the other's compiled grammar table. A class declared in an entry cannot be
|
|
9
|
+
* re-exported by a sibling entry without dragging that entry's imports along.
|
|
10
|
+
*/
|
|
11
|
+
/** Structured failure from the public direct Jess parser. */
|
|
12
|
+
var JessParseError = class extends SyntaxError {
|
|
13
|
+
code = "parse/syntax-error";
|
|
14
|
+
offset;
|
|
15
|
+
expected;
|
|
16
|
+
line;
|
|
17
|
+
column;
|
|
18
|
+
endLine;
|
|
19
|
+
endColumn;
|
|
20
|
+
reason;
|
|
21
|
+
fix;
|
|
22
|
+
constructor(offset, expected, options = {}) {
|
|
23
|
+
const detail = expected.length > 0 ? ` Expected: ${expected.join(", ")}.` : "";
|
|
24
|
+
super(options.message ?? `Jess parser error.${detail}`);
|
|
25
|
+
this.name = "JessParseError";
|
|
26
|
+
this.offset = offset;
|
|
27
|
+
this.expected = expected;
|
|
28
|
+
this.line = options.line;
|
|
29
|
+
this.column = options.column;
|
|
30
|
+
this.endLine = options.endLine;
|
|
31
|
+
this.endColumn = options.endColumn;
|
|
32
|
+
this.reason = options.reason;
|
|
33
|
+
this.fix = options.fix;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/parse-with.ts
|
|
38
|
+
/**
|
|
39
|
+
* The `parse()` body and the `$apply`/`$extend` selector policy checks, shared
|
|
40
|
+
* by the two AST entries.
|
|
41
|
+
*
|
|
42
|
+
* The grammar table arrives as an argument rather than being chosen from a
|
|
43
|
+
* boolean inside this module: Node does not tree-shake, so a module that names
|
|
44
|
+
* both compiled tables executes both at load time. Each entry imports exactly
|
|
45
|
+
* the one table it parses with, and this module imports none.
|
|
46
|
+
*/
|
|
47
|
+
function isStylesheet(value) {
|
|
48
|
+
return typeof value === "object" && value !== null && "type" in value && value.type === "Stylesheet" && "rules" in value && Array.isArray(value.rules);
|
|
49
|
+
}
|
|
50
|
+
const DEFAULT_APPLY_SELECTOR_KINDS = ["class"];
|
|
51
|
+
const DEFAULT_EXTEND_SELECTOR_KINDS = ["class", "placeholder"];
|
|
52
|
+
function selectorPolicyError(message) {
|
|
53
|
+
return new JessParseError(0, [message]);
|
|
54
|
+
}
|
|
55
|
+
function isClassSelector(simple) {
|
|
56
|
+
return simple.interp === null && typeof simple.text === "string" && simple.text.startsWith(".") && simple.text.length > 1;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A lone parent-ref target (`&`). Admitted only on the extend path: `$extend &`
|
|
60
|
+
* parses and no-op-matches at eval, mirroring Less `:extend(&)`. `$apply` stays
|
|
61
|
+
* class-only — a parent-ref carries no utility class to compose.
|
|
62
|
+
*
|
|
63
|
+
* The parser admits `&` because it is a legitimate simple-selector SHAPE; that
|
|
64
|
+
* `$extend &` can only ever no-op (a selector cannot extend itself) is a SEMANTIC
|
|
65
|
+
* fact, not a parse concern. The no-op is surfaced (not rejected) as
|
|
66
|
+
* diagnostics-core lint `lint/no-self-extend`; see DESIGN-DECISIONS ledger X11.
|
|
67
|
+
*/
|
|
68
|
+
function isBareParentRef(simple) {
|
|
69
|
+
return simple.type === "SimpleSelector" && simple.interp === null && simple.text === "&";
|
|
70
|
+
}
|
|
71
|
+
function isTermAllowed(term, allowed, isExtend) {
|
|
72
|
+
if (term.type === "CompoundSelector") return allowed.has("compound") || term.value.length === 1 && isSimpleAllowed(term.value[0], allowed, isExtend);
|
|
73
|
+
return isSimpleAllowed(term, allowed, isExtend);
|
|
74
|
+
}
|
|
75
|
+
function isSimpleAllowed(simple, allowed, isExtend) {
|
|
76
|
+
if (isExtend && isBareParentRef(simple)) return true;
|
|
77
|
+
if (simple.type === "PseudoSelector") return allowed.has("simple") || allowed.has("pseudo");
|
|
78
|
+
return allowed.has("class") && isClassSelector(simple) || allowed.has("placeholder") && (0, _jesscss_core_ast.simpleSelectorIsPlaceholder)(simple) || allowed.has("simple") || allowed.has("basic");
|
|
79
|
+
}
|
|
80
|
+
function isBranchAllowed(branch, allowed, isExtend) {
|
|
81
|
+
return branch.type === "ComplexSelector" || branch.type === "RelativeSelector" ? allowed.has("complex") : isTermAllowed(branch, allowed, isExtend);
|
|
82
|
+
}
|
|
83
|
+
function validateSelectorList(label, selector, allowedKinds) {
|
|
84
|
+
const allowed = new Set(allowedKinds);
|
|
85
|
+
if (!selector.selectors.every((item) => isBranchAllowed(item, allowed, true))) throw selectorPolicyError(`${label} selector is not allowed by allowExtendSelectors.`);
|
|
86
|
+
}
|
|
87
|
+
function validateApply(node, allowedKinds) {
|
|
88
|
+
const allowed = new Set(allowedKinds);
|
|
89
|
+
if (!node.selectors.every((selector) => isTermAllowed(selector, allowed, false))) throw selectorPolicyError("$apply target is not allowed by allowApplySelectors.");
|
|
90
|
+
}
|
|
91
|
+
function validateRuleset(node, options) {
|
|
92
|
+
for (const instruction of node.extendInstructions ?? []) validateSelectorList("$extend", instruction.target, options.allowExtendSelectors);
|
|
93
|
+
validateStatements(node.rules, options);
|
|
94
|
+
}
|
|
95
|
+
function validateStatements(rules, options) {
|
|
96
|
+
for (const node of rules) switch (node.type) {
|
|
97
|
+
case "Ruleset":
|
|
98
|
+
validateRuleset(node, options);
|
|
99
|
+
break;
|
|
100
|
+
case "MixinDefinition":
|
|
101
|
+
case "For":
|
|
102
|
+
case "AtRuleBlock":
|
|
103
|
+
validateStatements(node.rules, options);
|
|
104
|
+
break;
|
|
105
|
+
case "If":
|
|
106
|
+
for (const branch of node.branches) validateStatements(branch.rules, options);
|
|
107
|
+
break;
|
|
108
|
+
case "Apply":
|
|
109
|
+
validateApply(node, options.allowApplySelectors);
|
|
110
|
+
break;
|
|
111
|
+
default: break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function validateJessOptions(document, options = {}) {
|
|
115
|
+
validateStatements(document.rules, {
|
|
116
|
+
allowApplySelectors: options.allowApplySelectors ?? DEFAULT_APPLY_SELECTOR_KINDS,
|
|
117
|
+
allowExtendSelectors: options.allowExtendSelectors ?? DEFAULT_EXTEND_SELECTOR_KINDS
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Line/column at a bare offset. The leftover-input offset is not the start of
|
|
122
|
+
* any span the run returned — `result.span` covers the text that *was*
|
|
123
|
+
* consumed — so the position has to be derived from the offset itself. Only
|
|
124
|
+
* ever reached on a throw path, so building the index here costs a parse
|
|
125
|
+
* nothing.
|
|
126
|
+
*/
|
|
127
|
+
function positionAt(input, offset) {
|
|
128
|
+
const { line, col } = (0, parseman.offsetToLineCol)((0, parseman.buildLineIndex)(input), offset);
|
|
129
|
+
return {
|
|
130
|
+
line,
|
|
131
|
+
column: col
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Line/column for a failure span. The compiled table without line tracking
|
|
136
|
+
* leaves a span's line fields unset, so fall back to deriving them; an error
|
|
137
|
+
* that reports an offset but no line is barely actionable in an editor.
|
|
138
|
+
*/
|
|
139
|
+
function lineOptions(input, span) {
|
|
140
|
+
if (span.startLine === void 0) return positionAt(input, span.start);
|
|
141
|
+
return {
|
|
142
|
+
line: span.startLine,
|
|
143
|
+
column: span.startColumn,
|
|
144
|
+
endLine: span.endLine,
|
|
145
|
+
endColumn: span.endColumn
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function parseWith(grammar, input, options = {}) {
|
|
149
|
+
const entry = grammar.Stylesheet;
|
|
150
|
+
const trivia = grammar.whitespace;
|
|
151
|
+
if (entry === void 0 || trivia === void 0) throw new TypeError("Jess AST grammar is missing its public document entry.");
|
|
152
|
+
const result = (0, parseman.run)(entry, input, {
|
|
153
|
+
trivia,
|
|
154
|
+
rootTrivia: { select: require_trivia_labels.commentTriviaLabels }
|
|
155
|
+
});
|
|
156
|
+
if (!result.ok) throw new JessParseError(result.span.start, result.expected, lineOptions(input, result.span));
|
|
157
|
+
if (result.unconsumedFrom !== null) {
|
|
158
|
+
if (isStylesheet(result.value) && result.value.rules.length > 0) throw new JessParseError(result.unconsumedFrom, [], {
|
|
159
|
+
message: "Unexpected Jess input after a complete stylesheet.",
|
|
160
|
+
reason: "The parser read a complete Jess stylesheet before this point, so the remaining text sits outside every rule, declaration, and at-rule.",
|
|
161
|
+
fix: "Delete the trailing text, or remove the extra \"}\" — over-closing a nested block ends the stylesheet early.",
|
|
162
|
+
...positionAt(input, result.unconsumedFrom)
|
|
163
|
+
});
|
|
164
|
+
throw new JessParseError(result.unconsumedFrom, [], {
|
|
165
|
+
message: "Unexpected Jess syntax.",
|
|
166
|
+
reason: "The parser could not read this token as the start of a Jess rule, declaration, assignment, or at-rule.",
|
|
167
|
+
fix: "Remove the token, or rewrite it as a selector block, a \"$name: value\" assignment, or an at-rule.",
|
|
168
|
+
...positionAt(input, result.unconsumedFrom)
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (!isStylesheet(result.value)) throw new JessParseError(result.span.end, [], {
|
|
172
|
+
message: "Jess parser did not produce a stylesheet.",
|
|
173
|
+
reason: "The Jess parser matched the input but returned a value that is not a stylesheet document.",
|
|
174
|
+
fix: "Report this as a parser bug with the source that triggered it.",
|
|
175
|
+
...positionAt(input, result.span.end)
|
|
176
|
+
});
|
|
177
|
+
const document = (0, _jesscss_core_ast.withTriviaMap)((0, _jesscss_core_ast.withSourceSpan)(result.value, result.span), (0, _jesscss_core_ast.createTriviaMapFromParseman)(input, result.rootTrivia?.index));
|
|
178
|
+
validateJessOptions(document, options);
|
|
179
|
+
return document;
|
|
180
|
+
}
|
|
181
|
+
//#endregion
|
|
182
|
+
Object.defineProperty(exports, "JessParseError", {
|
|
183
|
+
enumerable: true,
|
|
184
|
+
get: function() {
|
|
185
|
+
return JessParseError;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
Object.defineProperty(exports, "parseWith", {
|
|
189
|
+
enumerable: true,
|
|
190
|
+
get: function() {
|
|
191
|
+
return parseWith;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { t as commentTriviaLabels } from "./trivia-labels.js";
|
|
2
|
+
import { buildLineIndex, offsetToLineCol, run } from "parseman";
|
|
3
|
+
import { createTriviaMapFromParseman, simpleSelectorIsPlaceholder, withSourceSpan, withTriviaMap } from "@jesscss/core/ast";
|
|
4
|
+
//#region src/parse-error.ts
|
|
5
|
+
/**
|
|
6
|
+
* The public Jess parse failure lives in its own module so that both AST
|
|
7
|
+
* entries — `.` and `./positions` — can export it without either one reaching
|
|
8
|
+
* the other's compiled grammar table. A class declared in an entry cannot be
|
|
9
|
+
* re-exported by a sibling entry without dragging that entry's imports along.
|
|
10
|
+
*/
|
|
11
|
+
/** Structured failure from the public direct Jess parser. */
|
|
12
|
+
var JessParseError = class extends SyntaxError {
|
|
13
|
+
code = "parse/syntax-error";
|
|
14
|
+
offset;
|
|
15
|
+
expected;
|
|
16
|
+
line;
|
|
17
|
+
column;
|
|
18
|
+
endLine;
|
|
19
|
+
endColumn;
|
|
20
|
+
reason;
|
|
21
|
+
fix;
|
|
22
|
+
constructor(offset, expected, options = {}) {
|
|
23
|
+
const detail = expected.length > 0 ? ` Expected: ${expected.join(", ")}.` : "";
|
|
24
|
+
super(options.message ?? `Jess parser error.${detail}`);
|
|
25
|
+
this.name = "JessParseError";
|
|
26
|
+
this.offset = offset;
|
|
27
|
+
this.expected = expected;
|
|
28
|
+
this.line = options.line;
|
|
29
|
+
this.column = options.column;
|
|
30
|
+
this.endLine = options.endLine;
|
|
31
|
+
this.endColumn = options.endColumn;
|
|
32
|
+
this.reason = options.reason;
|
|
33
|
+
this.fix = options.fix;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/parse-with.ts
|
|
38
|
+
/**
|
|
39
|
+
* The `parse()` body and the `$apply`/`$extend` selector policy checks, shared
|
|
40
|
+
* by the two AST entries.
|
|
41
|
+
*
|
|
42
|
+
* The grammar table arrives as an argument rather than being chosen from a
|
|
43
|
+
* boolean inside this module: Node does not tree-shake, so a module that names
|
|
44
|
+
* both compiled tables executes both at load time. Each entry imports exactly
|
|
45
|
+
* the one table it parses with, and this module imports none.
|
|
46
|
+
*/
|
|
47
|
+
function isStylesheet(value) {
|
|
48
|
+
return typeof value === "object" && value !== null && "type" in value && value.type === "Stylesheet" && "rules" in value && Array.isArray(value.rules);
|
|
49
|
+
}
|
|
50
|
+
const DEFAULT_APPLY_SELECTOR_KINDS = ["class"];
|
|
51
|
+
const DEFAULT_EXTEND_SELECTOR_KINDS = ["class", "placeholder"];
|
|
52
|
+
function selectorPolicyError(message) {
|
|
53
|
+
return new JessParseError(0, [message]);
|
|
54
|
+
}
|
|
55
|
+
function isClassSelector(simple) {
|
|
56
|
+
return simple.interp === null && typeof simple.text === "string" && simple.text.startsWith(".") && simple.text.length > 1;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A lone parent-ref target (`&`). Admitted only on the extend path: `$extend &`
|
|
60
|
+
* parses and no-op-matches at eval, mirroring Less `:extend(&)`. `$apply` stays
|
|
61
|
+
* class-only — a parent-ref carries no utility class to compose.
|
|
62
|
+
*
|
|
63
|
+
* The parser admits `&` because it is a legitimate simple-selector SHAPE; that
|
|
64
|
+
* `$extend &` can only ever no-op (a selector cannot extend itself) is a SEMANTIC
|
|
65
|
+
* fact, not a parse concern. The no-op is surfaced (not rejected) as
|
|
66
|
+
* diagnostics-core lint `lint/no-self-extend`; see DESIGN-DECISIONS ledger X11.
|
|
67
|
+
*/
|
|
68
|
+
function isBareParentRef(simple) {
|
|
69
|
+
return simple.type === "SimpleSelector" && simple.interp === null && simple.text === "&";
|
|
70
|
+
}
|
|
71
|
+
function isTermAllowed(term, allowed, isExtend) {
|
|
72
|
+
if (term.type === "CompoundSelector") return allowed.has("compound") || term.value.length === 1 && isSimpleAllowed(term.value[0], allowed, isExtend);
|
|
73
|
+
return isSimpleAllowed(term, allowed, isExtend);
|
|
74
|
+
}
|
|
75
|
+
function isSimpleAllowed(simple, allowed, isExtend) {
|
|
76
|
+
if (isExtend && isBareParentRef(simple)) return true;
|
|
77
|
+
if (simple.type === "PseudoSelector") return allowed.has("simple") || allowed.has("pseudo");
|
|
78
|
+
return allowed.has("class") && isClassSelector(simple) || allowed.has("placeholder") && simpleSelectorIsPlaceholder(simple) || allowed.has("simple") || allowed.has("basic");
|
|
79
|
+
}
|
|
80
|
+
function isBranchAllowed(branch, allowed, isExtend) {
|
|
81
|
+
return branch.type === "ComplexSelector" || branch.type === "RelativeSelector" ? allowed.has("complex") : isTermAllowed(branch, allowed, isExtend);
|
|
82
|
+
}
|
|
83
|
+
function validateSelectorList(label, selector, allowedKinds) {
|
|
84
|
+
const allowed = new Set(allowedKinds);
|
|
85
|
+
if (!selector.selectors.every((item) => isBranchAllowed(item, allowed, true))) throw selectorPolicyError(`${label} selector is not allowed by allowExtendSelectors.`);
|
|
86
|
+
}
|
|
87
|
+
function validateApply(node, allowedKinds) {
|
|
88
|
+
const allowed = new Set(allowedKinds);
|
|
89
|
+
if (!node.selectors.every((selector) => isTermAllowed(selector, allowed, false))) throw selectorPolicyError("$apply target is not allowed by allowApplySelectors.");
|
|
90
|
+
}
|
|
91
|
+
function validateRuleset(node, options) {
|
|
92
|
+
for (const instruction of node.extendInstructions ?? []) validateSelectorList("$extend", instruction.target, options.allowExtendSelectors);
|
|
93
|
+
validateStatements(node.rules, options);
|
|
94
|
+
}
|
|
95
|
+
function validateStatements(rules, options) {
|
|
96
|
+
for (const node of rules) switch (node.type) {
|
|
97
|
+
case "Ruleset":
|
|
98
|
+
validateRuleset(node, options);
|
|
99
|
+
break;
|
|
100
|
+
case "MixinDefinition":
|
|
101
|
+
case "For":
|
|
102
|
+
case "AtRuleBlock":
|
|
103
|
+
validateStatements(node.rules, options);
|
|
104
|
+
break;
|
|
105
|
+
case "If":
|
|
106
|
+
for (const branch of node.branches) validateStatements(branch.rules, options);
|
|
107
|
+
break;
|
|
108
|
+
case "Apply":
|
|
109
|
+
validateApply(node, options.allowApplySelectors);
|
|
110
|
+
break;
|
|
111
|
+
default: break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function validateJessOptions(document, options = {}) {
|
|
115
|
+
validateStatements(document.rules, {
|
|
116
|
+
allowApplySelectors: options.allowApplySelectors ?? DEFAULT_APPLY_SELECTOR_KINDS,
|
|
117
|
+
allowExtendSelectors: options.allowExtendSelectors ?? DEFAULT_EXTEND_SELECTOR_KINDS
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Line/column at a bare offset. The leftover-input offset is not the start of
|
|
122
|
+
* any span the run returned — `result.span` covers the text that *was*
|
|
123
|
+
* consumed — so the position has to be derived from the offset itself. Only
|
|
124
|
+
* ever reached on a throw path, so building the index here costs a parse
|
|
125
|
+
* nothing.
|
|
126
|
+
*/
|
|
127
|
+
function positionAt(input, offset) {
|
|
128
|
+
const { line, col } = offsetToLineCol(buildLineIndex(input), offset);
|
|
129
|
+
return {
|
|
130
|
+
line,
|
|
131
|
+
column: col
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Line/column for a failure span. The compiled table without line tracking
|
|
136
|
+
* leaves a span's line fields unset, so fall back to deriving them; an error
|
|
137
|
+
* that reports an offset but no line is barely actionable in an editor.
|
|
138
|
+
*/
|
|
139
|
+
function lineOptions(input, span) {
|
|
140
|
+
if (span.startLine === void 0) return positionAt(input, span.start);
|
|
141
|
+
return {
|
|
142
|
+
line: span.startLine,
|
|
143
|
+
column: span.startColumn,
|
|
144
|
+
endLine: span.endLine,
|
|
145
|
+
endColumn: span.endColumn
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function parseWith(grammar, input, options = {}) {
|
|
149
|
+
const entry = grammar.Stylesheet;
|
|
150
|
+
const trivia = grammar.whitespace;
|
|
151
|
+
if (entry === void 0 || trivia === void 0) throw new TypeError("Jess AST grammar is missing its public document entry.");
|
|
152
|
+
const result = run(entry, input, {
|
|
153
|
+
trivia,
|
|
154
|
+
rootTrivia: { select: commentTriviaLabels }
|
|
155
|
+
});
|
|
156
|
+
if (!result.ok) throw new JessParseError(result.span.start, result.expected, lineOptions(input, result.span));
|
|
157
|
+
if (result.unconsumedFrom !== null) {
|
|
158
|
+
if (isStylesheet(result.value) && result.value.rules.length > 0) throw new JessParseError(result.unconsumedFrom, [], {
|
|
159
|
+
message: "Unexpected Jess input after a complete stylesheet.",
|
|
160
|
+
reason: "The parser read a complete Jess stylesheet before this point, so the remaining text sits outside every rule, declaration, and at-rule.",
|
|
161
|
+
fix: "Delete the trailing text, or remove the extra \"}\" — over-closing a nested block ends the stylesheet early.",
|
|
162
|
+
...positionAt(input, result.unconsumedFrom)
|
|
163
|
+
});
|
|
164
|
+
throw new JessParseError(result.unconsumedFrom, [], {
|
|
165
|
+
message: "Unexpected Jess syntax.",
|
|
166
|
+
reason: "The parser could not read this token as the start of a Jess rule, declaration, assignment, or at-rule.",
|
|
167
|
+
fix: "Remove the token, or rewrite it as a selector block, a \"$name: value\" assignment, or an at-rule.",
|
|
168
|
+
...positionAt(input, result.unconsumedFrom)
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (!isStylesheet(result.value)) throw new JessParseError(result.span.end, [], {
|
|
172
|
+
message: "Jess parser did not produce a stylesheet.",
|
|
173
|
+
reason: "The Jess parser matched the input but returned a value that is not a stylesheet document.",
|
|
174
|
+
fix: "Report this as a parser bug with the source that triggered it.",
|
|
175
|
+
...positionAt(input, result.span.end)
|
|
176
|
+
});
|
|
177
|
+
const document = withTriviaMap(withSourceSpan(result.value, result.span), createTriviaMapFromParseman(input, result.rootTrivia?.index));
|
|
178
|
+
validateJessOptions(document, options);
|
|
179
|
+
return document;
|
|
180
|
+
}
|
|
181
|
+
//#endregion
|
|
182
|
+
export { JessParseError as n, parseWith as t };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/trivia-labels.ts
|
|
2
|
+
/**
|
|
3
|
+
* Root-trivia label selection for the Jess grammar.
|
|
4
|
+
*
|
|
5
|
+
* This is a plain fact about the grammar's trivia arm labels, so it lives in a
|
|
6
|
+
* leaf module with no imports. Reading it from the CST entry instead would make
|
|
7
|
+
* every consumer of the package entry load the compiled CST grammar tables:
|
|
8
|
+
* Node's ESM loader does not tree-shake, so an unused named import still
|
|
9
|
+
* executes the module it is taken from, and each table is multiple megabytes.
|
|
10
|
+
*/
|
|
11
|
+
const commentTriviaLabels = ["comment"];
|
|
12
|
+
//#endregion
|
|
13
|
+
Object.defineProperty(exports, "commentTriviaLabels", {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
get: function() {
|
|
16
|
+
return commentTriviaLabels;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/trivia-labels.ts
|
|
2
|
+
/**
|
|
3
|
+
* Root-trivia label selection for the Jess grammar.
|
|
4
|
+
*
|
|
5
|
+
* This is a plain fact about the grammar's trivia arm labels, so it lives in a
|
|
6
|
+
* leaf module with no imports. Reading it from the CST entry instead would make
|
|
7
|
+
* every consumer of the package entry load the compiled CST grammar tables:
|
|
8
|
+
* Node's ESM loader does not tree-shake, so an unused named import still
|
|
9
|
+
* executes the module it is taken from, and each table is multiple megabytes.
|
|
10
|
+
*/
|
|
11
|
+
const commentTriviaLabels = ["comment"];
|
|
12
|
+
//#endregion
|
|
13
|
+
export { commentTriviaLabels as t };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_trivia_labels = require("../chunks/trivia-labels.cjs");
|
|
3
|
+
let _jesscss_css_parser_cst_host = require("@jesscss/css-parser/cst-host");
|
|
4
|
+
let src_grammar_cst_positions_js = require("../grammar/cst/positions.cjs");
|
|
5
|
+
//#region src/cst/positions.ts
|
|
6
|
+
/**
|
|
7
|
+
* The line-aware Jess CST entry: the `./cst` functions bound to the compiled
|
|
8
|
+
* table that tracks lines and columns. Tolerant error recovery is a property of
|
|
9
|
+
* the CST runner, not of the table, so `result.errors` is collected here
|
|
10
|
+
* exactly as it is on `./cst` — this entry adds line/column facts and nothing
|
|
11
|
+
* else. It never loads the offsets-only table.
|
|
12
|
+
*/
|
|
13
|
+
/** Parse Jess to a CST whose spans carry line and column facts. */
|
|
14
|
+
function parseJessCst(input, startRule = "Stylesheet", options) {
|
|
15
|
+
return (0, _jesscss_css_parser_cst_host.parseCst)(src_grammar_cst_positions_js.jessCstPositionsGrammar, input, startRule, options, require_trivia_labels.commentTriviaLabels);
|
|
16
|
+
}
|
|
17
|
+
/** Incremental (`.edit()`-able) Jess document with line and column facts. */
|
|
18
|
+
function parseJessDoc(input, startRule = "Stylesheet") {
|
|
19
|
+
return (0, _jesscss_css_parser_cst_host.parseDocCst)(src_grammar_cst_positions_js.jessCstPositionsGrammar, input, startRule);
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
exports.commentTriviaLabels = require_trivia_labels.commentTriviaLabels;
|
|
23
|
+
exports.parseJessCst = parseJessCst;
|
|
24
|
+
exports.parseJessDoc = parseJessDoc;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { t as commentTriviaLabels } from "../chunks/trivia-labels.js";
|
|
2
|
+
import { parseCst, parseDocCst } from "@jesscss/css-parser/cst-host";
|
|
3
|
+
import { jessCstPositionsGrammar } from "../grammar/cst/positions.js";
|
|
4
|
+
//#region src/cst/positions.ts
|
|
5
|
+
/**
|
|
6
|
+
* The line-aware Jess CST entry: the `./cst` functions bound to the compiled
|
|
7
|
+
* table that tracks lines and columns. Tolerant error recovery is a property of
|
|
8
|
+
* the CST runner, not of the table, so `result.errors` is collected here
|
|
9
|
+
* exactly as it is on `./cst` — this entry adds line/column facts and nothing
|
|
10
|
+
* else. It never loads the offsets-only table.
|
|
11
|
+
*/
|
|
12
|
+
/** Parse Jess to a CST whose spans carry line and column facts. */
|
|
13
|
+
function parseJessCst(input, startRule = "Stylesheet", options) {
|
|
14
|
+
return parseCst(jessCstPositionsGrammar, input, startRule, options, commentTriviaLabels);
|
|
15
|
+
}
|
|
16
|
+
/** Incremental (`.edit()`-able) Jess document with line and column facts. */
|
|
17
|
+
function parseJessDoc(input, startRule = "Stylesheet") {
|
|
18
|
+
return parseDocCst(jessCstPositionsGrammar, input, startRule);
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { commentTriviaLabels, parseJessCst, parseJessDoc };
|
package/lib/cst.cjs
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
3
|
-
let
|
|
2
|
+
const require_trivia_labels = require("./chunks/trivia-labels.cjs");
|
|
3
|
+
let _jesscss_css_parser_cst_host = require("@jesscss/css-parser/cst-host");
|
|
4
|
+
let src_grammar_cst_js = require("./grammar/cst.cjs");
|
|
4
5
|
//#region src/cst.ts
|
|
6
|
+
/**
|
|
7
|
+
* Parse Jess to a CST. Spans carry offsets only; for line/column facts import
|
|
8
|
+
* the same functions from `@jesscss/jess-parser/cst/positions`, which binds the
|
|
9
|
+
* line-aware compiled table. This entry never loads that table.
|
|
10
|
+
*/
|
|
5
11
|
function parseJessCst(input, startRule = "Stylesheet", options) {
|
|
6
|
-
return (0,
|
|
12
|
+
return (0, _jesscss_css_parser_cst_host.parseCst)(src_grammar_cst_js.jessCstGrammar, input, startRule, options, require_trivia_labels.commentTriviaLabels);
|
|
7
13
|
}
|
|
8
|
-
/** Incremental (`.edit()`-able) Jess document —
|
|
14
|
+
/** Incremental (`.edit()`-able) Jess document — see `parseDocCst`. */
|
|
9
15
|
function parseJessDoc(input, startRule = "Stylesheet") {
|
|
10
|
-
return (0,
|
|
16
|
+
return (0, _jesscss_css_parser_cst_host.parseDocCst)(src_grammar_cst_js.jessCstGrammar, input, startRule);
|
|
11
17
|
}
|
|
12
18
|
//#endregion
|
|
19
|
+
exports.commentTriviaLabels = require_trivia_labels.commentTriviaLabels;
|
|
13
20
|
exports.parseJessCst = parseJessCst;
|
|
14
21
|
exports.parseJessDoc = parseJessDoc;
|
package/lib/cst.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import { type CssCstNode, type CssCstParseOptions, type CssCstParseResult, type ParseDoc } from '@jesscss/css-parser/cst';
|
|
1
|
+
import { type CssCstNode, type CssCstParseOptions, type CssCstParseResult, type ParseDoc } from '@jesscss/css-parser/cst-host';
|
|
2
|
+
export { commentTriviaLabels } from './trivia-labels.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parse Jess to a CST. Spans carry offsets only; for line/column facts import
|
|
5
|
+
* the same functions from `@jesscss/jess-parser/cst/positions`, which binds the
|
|
6
|
+
* line-aware compiled table. This entry never loads that table.
|
|
7
|
+
*/
|
|
2
8
|
export declare function parseJessCst(input: string, startRule?: string, options?: CssCstParseOptions): CssCstParseResult;
|
|
3
|
-
/** Incremental (`.edit()`-able) Jess document —
|
|
9
|
+
/** Incremental (`.edit()`-able) Jess document — see `parseDocCst`. */
|
|
4
10
|
export declare function parseJessDoc(input: string, startRule?: string): ParseDoc<CssCstNode>;
|
|
5
|
-
export type { CssCstChild as JessCstChild, CssCstError as JessCstError, CssCstLeaf as JessCstLeaf, CssCstNode as JessCstNode, CssCstParseOptions as JessCstParseOptions, CssCstParseResult as JessCstParseResult, CssCstType as JessCstType } from '@jesscss/css-parser/cst';
|
|
11
|
+
export type { CssCstChild as JessCstChild, CssCstError as JessCstError, CssCstLeaf as JessCstLeaf, CssCstNode as JessCstNode, CssCstParseOptions as JessCstParseOptions, CssCstParseResult as JessCstParseResult, CssCstType as JessCstType } from '@jesscss/css-parser/cst-host';
|
package/lib/cst.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { parseCst, parseDocCst } from "@jesscss/css-parser/cst";
|
|
1
|
+
import { t as commentTriviaLabels } from "./chunks/trivia-labels.js";
|
|
2
|
+
import { parseCst, parseDocCst } from "@jesscss/css-parser/cst-host";
|
|
3
|
+
import { jessCstGrammar } from "./grammar/cst.js";
|
|
3
4
|
//#region src/cst.ts
|
|
5
|
+
/**
|
|
6
|
+
* Parse Jess to a CST. Spans carry offsets only; for line/column facts import
|
|
7
|
+
* the same functions from `@jesscss/jess-parser/cst/positions`, which binds the
|
|
8
|
+
* line-aware compiled table. This entry never loads that table.
|
|
9
|
+
*/
|
|
4
10
|
function parseJessCst(input, startRule = "Stylesheet", options) {
|
|
5
|
-
return parseCst(jessCstGrammar, input, startRule, options);
|
|
11
|
+
return parseCst(jessCstGrammar, input, startRule, options, commentTriviaLabels);
|
|
6
12
|
}
|
|
7
|
-
/** Incremental (`.edit()`-able) Jess document —
|
|
13
|
+
/** Incremental (`.edit()`-able) Jess document — see `parseDocCst`. */
|
|
8
14
|
function parseJessDoc(input, startRule = "Stylesheet") {
|
|
9
15
|
return parseDocCst(jessCstGrammar, input, startRule);
|
|
10
16
|
}
|
|
11
17
|
//#endregion
|
|
12
|
-
export { parseJessCst, parseJessDoc };
|
|
18
|
+
export { commentTriviaLabels, parseJessCst, parseJessDoc };
|