@jesscss/less-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 +55 -11
- package/lib/chunks/parse-with.cjs +172 -0
- package/lib/chunks/parse-with.js +161 -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.d.ts +8 -0
- package/lib/cst/positions.d.ts.map +1 -0
- package/lib/cst/positions.js +21 -0
- package/lib/cst.cjs +11 -4
- package/lib/cst.d.ts +8 -2
- package/lib/cst.d.ts.map +1 -1
- package/lib/cst.js +10 -4
- package/lib/grammar/ast/positions.cjs +28802 -0
- package/lib/grammar/ast/positions.d.ts +3 -0
- package/lib/grammar/ast/positions.d.ts.map +1 -0
- package/lib/grammar/ast/positions.js +28801 -0
- package/lib/grammar/ast.cjs +29126 -0
- package/lib/grammar/ast.d.ts +11 -0
- package/lib/grammar/ast.d.ts.map +1 -0
- package/lib/grammar/ast.js +29125 -0
- package/lib/grammar/cst/positions.cjs +28802 -0
- package/lib/grammar/cst/positions.d.ts +3 -0
- package/lib/grammar/cst/positions.d.ts.map +1 -0
- package/lib/grammar/cst/positions.js +28801 -0
- package/lib/grammar/cst.cjs +29127 -0
- package/lib/grammar/cst.d.ts +3 -0
- package/lib/grammar/cst.d.ts.map +1 -0
- package/lib/grammar/cst.js +29126 -0
- package/lib/grammar-helpers.d.ts +478 -0
- package/lib/grammar-helpers.d.ts.map +1 -0
- package/lib/grammar.d.ts +4 -1
- package/lib/grammar.d.ts.map +1 -1
- package/lib/index.cjs +25 -63
- package/lib/index.d.ts +17 -9
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +18 -57
- package/lib/parse-error.cjs +160 -0
- package/lib/parse-error.d.ts +26 -0
- package/lib/parse-error.d.ts.map +1 -1
- package/lib/parse-error.js +152 -0
- package/lib/parse-state.d.ts +39 -0
- package/lib/parse-state.d.ts.map +1 -0
- package/lib/parse-with.d.ts +25 -0
- package/lib/parse-with.d.ts.map +1 -0
- package/lib/positions.cjs +24 -0
- package/lib/positions.d.ts +20 -0
- package/lib/positions.d.ts.map +1 -0
- package/lib/positions.js +14 -0
- package/lib/trivia-labels.d.ts +11 -0
- package/lib/trivia-labels.d.ts.map +1 -0
- package/package.json +42 -12
- package/lib/grammar.cjs +0 -5
- package/lib/grammar.js +0 -2
- package/lib/grammar2.cjs +0 -141792
- package/lib/grammar2.js +0 -141733
package/README.md
CHANGED
|
@@ -9,11 +9,15 @@ The Less grammar, layered on the CSS base parser, with core-free CST entry point
|
|
|
9
9
|
|
|
10
10
|
## What it is
|
|
11
11
|
|
|
12
|
-
The Less grammar
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
The Less grammar extends the spec-aligned CSS base in
|
|
13
|
+
[`@jesscss/css-parser`](https://www.npmjs.com/package/@jesscss/css-parser):
|
|
14
|
+
unchanged CSS structure remains CSS-owned, and Less changes only the smallest
|
|
15
|
+
child, value slot, or reference its syntax requires. It adds `@variable` /
|
|
16
|
+
`@{interpolation}`, mixins, and the rest of Less. Parseman currently compiles
|
|
17
|
+
the CSS and Less host factories from shared recognition artifacts rather than
|
|
18
|
+
literally composing a terminal `cssGrammar` artifact; that macro boundary does
|
|
19
|
+
not relax the ownership rule. It is built on
|
|
20
|
+
[parseman](https://www.npmjs.com/package/parseman) — **the fastest
|
|
17
21
|
general-purpose JavaScript parser** in its
|
|
18
22
|
[published benchmarks](https://matthew-dean.github.io/parseman/guide/benchmarks)
|
|
19
23
|
(see `@jesscss/css-parser` for figures and engineering details). It is the parser
|
|
@@ -73,9 +77,49 @@ Pass a different `startRule` (any capitalized grammar rule, e.g. `'SelectorList'
|
|
|
73
77
|
| --- | --- | --- |
|
|
74
78
|
| `@jesscss/less-parser/cst` | `parseLessCst` | Core-free parse of a Less string to a CST. |
|
|
75
79
|
| `@jesscss/less-parser/cst` | `LessCstNode`, `LessCstLeaf`, `LessCstError`, `LessCstChild`, `LessCstParseResult`, `LessCstType` (types) | CST type definitions (aliases of the shared `@jesscss/css-parser/cst` types). |
|
|
76
|
-
| `@jesscss/less-parser/grammar` | `lessGrammar` | The compiled Less grammar (a rule map). Extend it with `compose()` or drive it directly with parseman's `run`. |
|
|
80
|
+
| `@jesscss/less-parser/grammar` | `lessGrammar` | The compiled Less AST grammar (a rule map). Extend it with `compose()` or drive it directly with parseman's `run`. See the variant table below. |
|
|
77
81
|
| `@jesscss/less-parser` (`.`) | `parse` | Parse Less directly to canonical AST v2 `Stylesheet`. It does not load the CST grammar. |
|
|
78
82
|
|
|
83
|
+
### Line-aware entries
|
|
84
|
+
|
|
85
|
+
`parse` and the CST parsers come in two bindings, one per compiled table, so an
|
|
86
|
+
entry never loads a table it does not parse with:
|
|
87
|
+
|
|
88
|
+
| Entry | Export | Tree | Positions |
|
|
89
|
+
| --- | --- | --- | --- |
|
|
90
|
+
| `@jesscss/less-parser` (`.`) | `parse` | AST | no |
|
|
91
|
+
| `@jesscss/less-parser/positions` | `parse` | AST | yes |
|
|
92
|
+
| `@jesscss/less-parser/cst` | `parseLessCst`, `parseLessDoc` | CST | no |
|
|
93
|
+
| `@jesscss/less-parser/cst/positions` | `parseLessCst`, `parseLessDoc` | CST | yes |
|
|
94
|
+
|
|
95
|
+
The `/positions` entries export the same names bound to the line-aware table:
|
|
96
|
+
switching is a change of import specifier, not of call site.
|
|
97
|
+
|
|
98
|
+
### Choosing a grammar build
|
|
99
|
+
|
|
100
|
+
Each compiled grammar is a standalone multi-megabyte artifact, so the four
|
|
101
|
+
variants ship as four separate files. Importing one never loads the others.
|
|
102
|
+
Pick by the two questions the subpath name answers — which tree, and whether
|
|
103
|
+
source positions are tracked:
|
|
104
|
+
|
|
105
|
+
| Subpath | Export | Tree | Positions |
|
|
106
|
+
| --- | --- | --- | --- |
|
|
107
|
+
| `@jesscss/less-parser/grammar/ast` | `lessGrammar` | AST | no |
|
|
108
|
+
| `@jesscss/less-parser/grammar/ast/positions` | `lessPositionsGrammar` | AST | yes |
|
|
109
|
+
| `@jesscss/less-parser/grammar/cst` | `lessCstGrammar` | CST | no |
|
|
110
|
+
| `@jesscss/less-parser/grammar/cst/positions` | `lessCstPositionsGrammar` | CST | yes |
|
|
111
|
+
|
|
112
|
+
`@jesscss/less-parser/grammar` is an alias for `/grammar/ast`, the build the
|
|
113
|
+
shipping `parse()` route uses. It is not a barrel: it exposes the AST variant
|
|
114
|
+
only, so importing it cannot pull the other three in.
|
|
115
|
+
|
|
116
|
+
The positions variants set `startLine`/`startColumn` on every span. There is no
|
|
117
|
+
`trackLines` option: an option would force one module to name both tables, and
|
|
118
|
+
Node executes every module it statically imports, so the choice is which entry
|
|
119
|
+
you import. Error tolerance is not a property of a build — the CST runner
|
|
120
|
+
collects `result.errors` on either CST variant.
|
|
121
|
+
|
|
122
|
+
|
|
79
123
|
## Default CST shape
|
|
80
124
|
|
|
81
125
|
The CST is parseman's, produced by the shared `cssCstBuildHost`. Three kinds of node:
|
|
@@ -95,7 +139,7 @@ Parsing `@c: red;\n.foo { color: @c; }` yields (abridged):
|
|
|
95
139
|
{ "_tag": "node", "type": "VarDeclaration", "grammarType": "VarDeclaration", "span": { "start": 0, "end": 8 },
|
|
96
140
|
"children": [
|
|
97
141
|
{ "_tag": "leaf", "value": "@c" }, { "_tag": "leaf", "value": ":" },
|
|
98
|
-
{ "_tag": "node", "type": "
|
|
142
|
+
{ "_tag": "node", "type": "Keyword", "grammarType": "Keyword",
|
|
99
143
|
"children": [ { "_tag": "leaf", "value": "red" } ] },
|
|
100
144
|
{ "_tag": "leaf", "value": ";" }
|
|
101
145
|
] },
|
|
@@ -115,9 +159,9 @@ Parsing `@c: red;\n.foo { color: @c; }` yields (abridged):
|
|
|
115
159
|
}
|
|
116
160
|
```
|
|
117
161
|
|
|
118
|
-
Note the Less-specific nodes: a top-level `@c: …` becomes a `VarDeclaration`, a `@c` value becomes a `Reference
|
|
162
|
+
Note the Less-specific nodes: a top-level `@c: …` becomes a `VarDeclaration`, and a `@c` value becomes a `Reference`. The color keyword `red` parses as a plain `Keyword` — the same node every dialect uses (NamedColor→Keyword convergence); its colour-ness is resolved only when it is operated on.
|
|
119
163
|
|
|
120
|
-
Pass `{ collapse: true }` to unwrap single-child wrapper types (`Reference`, `
|
|
164
|
+
Pass `{ collapse: true }` to unwrap single-child wrapper types (`Reference`, `InterpolatedSelector`) into their child.
|
|
121
165
|
|
|
122
166
|
### Name-independent condition arguments
|
|
123
167
|
|
|
@@ -133,7 +177,7 @@ The grammar is decoupled from the tree it builds. Every capitalized rule is a pa
|
|
|
133
177
|
import { run } from 'parseman'
|
|
134
178
|
import { lessGrammar } from '@jesscss/less-parser/grammar'
|
|
135
179
|
|
|
136
|
-
const myHost = (type, children, fields, span) => ({ type, span,
|
|
180
|
+
const myHost = (type, children, fields, span) => ({ type, span, rules: children.filter(Boolean) })
|
|
137
181
|
|
|
138
182
|
const result = run(lessGrammar.Stylesheet, '@c: red; .foo { color: @c; }', {
|
|
139
183
|
build: myHost,
|
|
@@ -148,7 +192,7 @@ The `BuildHost` signature (from parseman):
|
|
|
148
192
|
```ts
|
|
149
193
|
type BuildHost = (
|
|
150
194
|
type: string,
|
|
151
|
-
|
|
195
|
+
rules: readonly unknown[],
|
|
152
196
|
fields: FieldMap | undefined,
|
|
153
197
|
span: { start: number; end: number },
|
|
154
198
|
rawChildren: readonly unknown[],
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
const require_parse_error = require("../parse-error.cjs");
|
|
2
|
+
const require_trivia_labels = require("./trivia-labels.cjs");
|
|
3
|
+
let parseman = require("parseman");
|
|
4
|
+
let _jesscss_core_diagnostics = require("@jesscss/core/diagnostics");
|
|
5
|
+
let _jesscss_core_ast = require("@jesscss/core/ast");
|
|
6
|
+
Object.freeze({ mathMode: "parens-division" });
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/parse-with.ts
|
|
9
|
+
/**
|
|
10
|
+
* The `parse()`/`safeParse()` bodies, shared by the two AST entries.
|
|
11
|
+
*
|
|
12
|
+
* The grammar table arrives as an argument rather than being chosen from a
|
|
13
|
+
* boolean inside this module: Node does not tree-shake, so a module that names
|
|
14
|
+
* both compiled tables executes both at load time. Each entry imports exactly
|
|
15
|
+
* the one table it parses with, and this module imports none.
|
|
16
|
+
*/
|
|
17
|
+
const EMPTY_LAYOUT = Object.freeze([]);
|
|
18
|
+
const SPACE_LAYOUT = Object.freeze([" "]);
|
|
19
|
+
function isStylesheet(value) {
|
|
20
|
+
return typeof value === "object" && value !== null && "type" in value && value.type === "Stylesheet" && "rules" in value && Array.isArray(value.rules);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The one currently typed Less import-query feature is a `Block` containing a
|
|
24
|
+
* colon `Operation`. Its right operand already carries a parser source start,
|
|
25
|
+
* so an exact trivia lookup can retain the comment boundary without walking or
|
|
26
|
+
* classifying source bytes. Opaque tail forms keep their existing byte owner.
|
|
27
|
+
*/
|
|
28
|
+
function retainTypedImportTailTrivia(statement, trivia) {
|
|
29
|
+
const prelude = statement.prelude;
|
|
30
|
+
if (prelude?.type !== "Sequence") return false;
|
|
31
|
+
const tail = prelude.parts[1];
|
|
32
|
+
if (tail?.type !== "Block" || (0, _jesscss_core_ast.isValueSlotArray)(tail.value) || tail.value.type !== "Operation" || tail.value.operator !== ":") return false;
|
|
33
|
+
const rightStart = (0, _jesscss_core_ast.sourceStartOf)(tail.value.right);
|
|
34
|
+
if (rightStart === _jesscss_core_ast.NO_SPAN) return false;
|
|
35
|
+
const between = trivia.lookup(rightStart, "before");
|
|
36
|
+
if (between === void 0) return false;
|
|
37
|
+
(0, _jesscss_core_ast.withValueBoundaryTrivia)(tail.value, EMPTY_LAYOUT, {
|
|
38
|
+
before: null,
|
|
39
|
+
between,
|
|
40
|
+
after: null
|
|
41
|
+
});
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Project only root-hoisted CSS-import boundaries through the canonical trivia
|
|
46
|
+
* adapter. The grammar carries the statement edges and typed-tail start in
|
|
47
|
+
* fixed private Smi slots, so each lookup names an exact parser-owned boundary
|
|
48
|
+
* and public source provenance, AST/CST shape, and bytes remain unchanged.
|
|
49
|
+
*/
|
|
50
|
+
function retainRootImportBoundaryTrivia(document, trivia) {
|
|
51
|
+
if (trivia === void 0) return;
|
|
52
|
+
for (const rule of document.rules) {
|
|
53
|
+
if (rule.type !== "AtRuleStatement") continue;
|
|
54
|
+
const start = (0, _jesscss_core_ast.importSourceStartOf)(rule);
|
|
55
|
+
if (start === _jesscss_core_ast.NO_SPAN) continue;
|
|
56
|
+
const tailStart = (0, _jesscss_core_ast.importTailStartOf)(rule);
|
|
57
|
+
const before = trivia.lookup(start + rule.name.length, "after") ?? null;
|
|
58
|
+
const between = tailStart === _jesscss_core_ast.NO_SPAN ? null : trivia.lookup(tailStart, "before") ?? null;
|
|
59
|
+
const after = trivia.lookup((0, _jesscss_core_ast.importSourceEndOf)(rule) - 1, "before") ?? null;
|
|
60
|
+
const hasInnerBoundary = retainTypedImportTailTrivia(rule, trivia);
|
|
61
|
+
if (before !== null || between !== null || after !== null || hasInnerBoundary) {
|
|
62
|
+
const prelude = rule.prelude;
|
|
63
|
+
(0, _jesscss_core_ast.withValueBoundaryTrivia)(prelude, (0, _jesscss_core_ast.valueLayoutOf)(prelude) ?? (prelude.type === "Sequence" ? SPACE_LAYOUT : EMPTY_LAYOUT), {
|
|
64
|
+
before,
|
|
65
|
+
between,
|
|
66
|
+
after
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Line/column at a bare offset. The leftover-input offset is not the start of
|
|
73
|
+
* any span the run returned — `result.span` covers the text that *was*
|
|
74
|
+
* consumed — so the position has to be derived from the offset itself. Only
|
|
75
|
+
* ever reached on a throw path, so building the index here costs a parse
|
|
76
|
+
* nothing.
|
|
77
|
+
*/
|
|
78
|
+
function positionAt(input, offset) {
|
|
79
|
+
const { line, col } = (0, parseman.offsetToLineCol)((0, parseman.buildLineIndex)(input), offset);
|
|
80
|
+
return {
|
|
81
|
+
line,
|
|
82
|
+
column: col
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Line/column for a failure span. The compiled table without line tracking
|
|
87
|
+
* leaves a span's line fields unset, so fall back to deriving them; an error
|
|
88
|
+
* that reports an offset but no line is barely actionable in an editor.
|
|
89
|
+
*/
|
|
90
|
+
function lineOptions(input, span) {
|
|
91
|
+
if (span.startLine === void 0) return positionAt(input, span.start);
|
|
92
|
+
return {
|
|
93
|
+
line: span.startLine,
|
|
94
|
+
column: span.startColumn,
|
|
95
|
+
endLine: span.endLine,
|
|
96
|
+
endColumn: span.endColumn
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function parseWith(grammar, input, options = {}) {
|
|
100
|
+
const entry = grammar.Stylesheet;
|
|
101
|
+
const trivia = grammar.whitespace;
|
|
102
|
+
if (entry === void 0 || trivia === void 0) throw new TypeError("Less AST grammar is missing its public document entry.");
|
|
103
|
+
const result = (0, parseman.run)(entry, input, {
|
|
104
|
+
trivia,
|
|
105
|
+
state: {
|
|
106
|
+
source: input,
|
|
107
|
+
mathMode: options.mathMode ?? "parens-division"
|
|
108
|
+
},
|
|
109
|
+
rootTrivia: { select: require_trivia_labels.commentTriviaLabels }
|
|
110
|
+
});
|
|
111
|
+
if (!result.ok) throw new require_parse_error.LessParseError(result.span.start, result.expected, lineOptions(input, result.span));
|
|
112
|
+
if (result.unconsumedFrom !== null) {
|
|
113
|
+
if (isStylesheet(result.value) && result.value.rules.length > 0) throw new require_parse_error.LessParseError(result.unconsumedFrom, [], {
|
|
114
|
+
message: "Unexpected Less input after a complete stylesheet.",
|
|
115
|
+
reason: "The parser consumed a complete Less stylesheet before this token, so the remaining text is not part of any rule, declaration, or at-rule.",
|
|
116
|
+
fix: "Remove the extra input or wrap it in valid Less syntax.",
|
|
117
|
+
...positionAt(input, result.unconsumedFrom)
|
|
118
|
+
});
|
|
119
|
+
throw new require_parse_error.LessParseError(result.unconsumedFrom, [], {
|
|
120
|
+
message: "Unexpected Less syntax.",
|
|
121
|
+
reason: "The parser could not match this token as the start of a Less rule, declaration, or at-rule.",
|
|
122
|
+
fix: "Remove the token or rewrite it as valid Less syntax.",
|
|
123
|
+
...positionAt(input, result.unconsumedFrom)
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (!isStylesheet(result.value)) throw new require_parse_error.LessParseError(result.span.end, [], {
|
|
127
|
+
...positionAt(input, result.span.end),
|
|
128
|
+
message: "Less parser did not produce a stylesheet.",
|
|
129
|
+
reason: "The Less parser matched the input but returned a value that is not a stylesheet document.",
|
|
130
|
+
fix: "Report this as a parser bug with the source that triggered it."
|
|
131
|
+
});
|
|
132
|
+
const triviaMap = (0, _jesscss_core_ast.createTriviaMapFromParseman)(input, result.rootTrivia?.index);
|
|
133
|
+
retainRootImportBoundaryTrivia(result.value, result.rootTrivia === void 0 ? void 0 : triviaMap);
|
|
134
|
+
return (0, _jesscss_core_ast.withTriviaMap)((0, _jesscss_core_ast.withSourceSpan)(result.value, result.span), triviaMap);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The `safeParse` body for the product plugin path. Parser packages own
|
|
138
|
+
* recognition facts; this boundary attaches file/source context once and
|
|
139
|
+
* returns normalized diagnostics for compiler and CLI consumers to render.
|
|
140
|
+
*/
|
|
141
|
+
function safeParseWith(grammar, filePath, input, options = {}) {
|
|
142
|
+
try {
|
|
143
|
+
return {
|
|
144
|
+
document: parseWith(grammar, input, options),
|
|
145
|
+
errors: [],
|
|
146
|
+
warnings: []
|
|
147
|
+
};
|
|
148
|
+
} catch (error) {
|
|
149
|
+
return {
|
|
150
|
+
errors: [(0, _jesscss_core_diagnostics.parserDiagnostic)({
|
|
151
|
+
dialect: "Less",
|
|
152
|
+
error,
|
|
153
|
+
filePath,
|
|
154
|
+
source: input
|
|
155
|
+
})],
|
|
156
|
+
warnings: []
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
Object.defineProperty(exports, "parseWith", {
|
|
162
|
+
enumerable: true,
|
|
163
|
+
get: function() {
|
|
164
|
+
return parseWith;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
Object.defineProperty(exports, "safeParseWith", {
|
|
168
|
+
enumerable: true,
|
|
169
|
+
get: function() {
|
|
170
|
+
return safeParseWith;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { LessParseError } from "../parse-error.js";
|
|
2
|
+
import { t as commentTriviaLabels } from "./trivia-labels.js";
|
|
3
|
+
import { buildLineIndex, offsetToLineCol, run } from "parseman";
|
|
4
|
+
import { parserDiagnostic } from "@jesscss/core/diagnostics";
|
|
5
|
+
import { NO_SPAN, createTriviaMapFromParseman, importSourceEndOf, importSourceStartOf, importTailStartOf, isValueSlotArray, sourceStartOf, valueLayoutOf, withSourceSpan, withTriviaMap, withValueBoundaryTrivia } from "@jesscss/core/ast";
|
|
6
|
+
Object.freeze({ mathMode: "parens-division" });
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/parse-with.ts
|
|
9
|
+
/**
|
|
10
|
+
* The `parse()`/`safeParse()` bodies, shared by the two AST entries.
|
|
11
|
+
*
|
|
12
|
+
* The grammar table arrives as an argument rather than being chosen from a
|
|
13
|
+
* boolean inside this module: Node does not tree-shake, so a module that names
|
|
14
|
+
* both compiled tables executes both at load time. Each entry imports exactly
|
|
15
|
+
* the one table it parses with, and this module imports none.
|
|
16
|
+
*/
|
|
17
|
+
const EMPTY_LAYOUT = Object.freeze([]);
|
|
18
|
+
const SPACE_LAYOUT = Object.freeze([" "]);
|
|
19
|
+
function isStylesheet(value) {
|
|
20
|
+
return typeof value === "object" && value !== null && "type" in value && value.type === "Stylesheet" && "rules" in value && Array.isArray(value.rules);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The one currently typed Less import-query feature is a `Block` containing a
|
|
24
|
+
* colon `Operation`. Its right operand already carries a parser source start,
|
|
25
|
+
* so an exact trivia lookup can retain the comment boundary without walking or
|
|
26
|
+
* classifying source bytes. Opaque tail forms keep their existing byte owner.
|
|
27
|
+
*/
|
|
28
|
+
function retainTypedImportTailTrivia(statement, trivia) {
|
|
29
|
+
const prelude = statement.prelude;
|
|
30
|
+
if (prelude?.type !== "Sequence") return false;
|
|
31
|
+
const tail = prelude.parts[1];
|
|
32
|
+
if (tail?.type !== "Block" || isValueSlotArray(tail.value) || tail.value.type !== "Operation" || tail.value.operator !== ":") return false;
|
|
33
|
+
const rightStart = sourceStartOf(tail.value.right);
|
|
34
|
+
if (rightStart === NO_SPAN) return false;
|
|
35
|
+
const between = trivia.lookup(rightStart, "before");
|
|
36
|
+
if (between === void 0) return false;
|
|
37
|
+
withValueBoundaryTrivia(tail.value, EMPTY_LAYOUT, {
|
|
38
|
+
before: null,
|
|
39
|
+
between,
|
|
40
|
+
after: null
|
|
41
|
+
});
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Project only root-hoisted CSS-import boundaries through the canonical trivia
|
|
46
|
+
* adapter. The grammar carries the statement edges and typed-tail start in
|
|
47
|
+
* fixed private Smi slots, so each lookup names an exact parser-owned boundary
|
|
48
|
+
* and public source provenance, AST/CST shape, and bytes remain unchanged.
|
|
49
|
+
*/
|
|
50
|
+
function retainRootImportBoundaryTrivia(document, trivia) {
|
|
51
|
+
if (trivia === void 0) return;
|
|
52
|
+
for (const rule of document.rules) {
|
|
53
|
+
if (rule.type !== "AtRuleStatement") continue;
|
|
54
|
+
const start = importSourceStartOf(rule);
|
|
55
|
+
if (start === NO_SPAN) continue;
|
|
56
|
+
const tailStart = importTailStartOf(rule);
|
|
57
|
+
const before = trivia.lookup(start + rule.name.length, "after") ?? null;
|
|
58
|
+
const between = tailStart === NO_SPAN ? null : trivia.lookup(tailStart, "before") ?? null;
|
|
59
|
+
const after = trivia.lookup(importSourceEndOf(rule) - 1, "before") ?? null;
|
|
60
|
+
const hasInnerBoundary = retainTypedImportTailTrivia(rule, trivia);
|
|
61
|
+
if (before !== null || between !== null || after !== null || hasInnerBoundary) {
|
|
62
|
+
const prelude = rule.prelude;
|
|
63
|
+
withValueBoundaryTrivia(prelude, valueLayoutOf(prelude) ?? (prelude.type === "Sequence" ? SPACE_LAYOUT : EMPTY_LAYOUT), {
|
|
64
|
+
before,
|
|
65
|
+
between,
|
|
66
|
+
after
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Line/column at a bare offset. The leftover-input offset is not the start of
|
|
73
|
+
* any span the run returned — `result.span` covers the text that *was*
|
|
74
|
+
* consumed — so the position has to be derived from the offset itself. Only
|
|
75
|
+
* ever reached on a throw path, so building the index here costs a parse
|
|
76
|
+
* nothing.
|
|
77
|
+
*/
|
|
78
|
+
function positionAt(input, offset) {
|
|
79
|
+
const { line, col } = offsetToLineCol(buildLineIndex(input), offset);
|
|
80
|
+
return {
|
|
81
|
+
line,
|
|
82
|
+
column: col
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Line/column for a failure span. The compiled table without line tracking
|
|
87
|
+
* leaves a span's line fields unset, so fall back to deriving them; an error
|
|
88
|
+
* that reports an offset but no line is barely actionable in an editor.
|
|
89
|
+
*/
|
|
90
|
+
function lineOptions(input, span) {
|
|
91
|
+
if (span.startLine === void 0) return positionAt(input, span.start);
|
|
92
|
+
return {
|
|
93
|
+
line: span.startLine,
|
|
94
|
+
column: span.startColumn,
|
|
95
|
+
endLine: span.endLine,
|
|
96
|
+
endColumn: span.endColumn
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function parseWith(grammar, input, options = {}) {
|
|
100
|
+
const entry = grammar.Stylesheet;
|
|
101
|
+
const trivia = grammar.whitespace;
|
|
102
|
+
if (entry === void 0 || trivia === void 0) throw new TypeError("Less AST grammar is missing its public document entry.");
|
|
103
|
+
const result = run(entry, input, {
|
|
104
|
+
trivia,
|
|
105
|
+
state: {
|
|
106
|
+
source: input,
|
|
107
|
+
mathMode: options.mathMode ?? "parens-division"
|
|
108
|
+
},
|
|
109
|
+
rootTrivia: { select: commentTriviaLabels }
|
|
110
|
+
});
|
|
111
|
+
if (!result.ok) throw new LessParseError(result.span.start, result.expected, lineOptions(input, result.span));
|
|
112
|
+
if (result.unconsumedFrom !== null) {
|
|
113
|
+
if (isStylesheet(result.value) && result.value.rules.length > 0) throw new LessParseError(result.unconsumedFrom, [], {
|
|
114
|
+
message: "Unexpected Less input after a complete stylesheet.",
|
|
115
|
+
reason: "The parser consumed a complete Less stylesheet before this token, so the remaining text is not part of any rule, declaration, or at-rule.",
|
|
116
|
+
fix: "Remove the extra input or wrap it in valid Less syntax.",
|
|
117
|
+
...positionAt(input, result.unconsumedFrom)
|
|
118
|
+
});
|
|
119
|
+
throw new LessParseError(result.unconsumedFrom, [], {
|
|
120
|
+
message: "Unexpected Less syntax.",
|
|
121
|
+
reason: "The parser could not match this token as the start of a Less rule, declaration, or at-rule.",
|
|
122
|
+
fix: "Remove the token or rewrite it as valid Less syntax.",
|
|
123
|
+
...positionAt(input, result.unconsumedFrom)
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (!isStylesheet(result.value)) throw new LessParseError(result.span.end, [], {
|
|
127
|
+
...positionAt(input, result.span.end),
|
|
128
|
+
message: "Less parser did not produce a stylesheet.",
|
|
129
|
+
reason: "The Less parser matched the input but returned a value that is not a stylesheet document.",
|
|
130
|
+
fix: "Report this as a parser bug with the source that triggered it."
|
|
131
|
+
});
|
|
132
|
+
const triviaMap = createTriviaMapFromParseman(input, result.rootTrivia?.index);
|
|
133
|
+
retainRootImportBoundaryTrivia(result.value, result.rootTrivia === void 0 ? void 0 : triviaMap);
|
|
134
|
+
return withTriviaMap(withSourceSpan(result.value, result.span), triviaMap);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The `safeParse` body for the product plugin path. Parser packages own
|
|
138
|
+
* recognition facts; this boundary attaches file/source context once and
|
|
139
|
+
* returns normalized diagnostics for compiler and CLI consumers to render.
|
|
140
|
+
*/
|
|
141
|
+
function safeParseWith(grammar, filePath, input, options = {}) {
|
|
142
|
+
try {
|
|
143
|
+
return {
|
|
144
|
+
document: parseWith(grammar, input, options),
|
|
145
|
+
errors: [],
|
|
146
|
+
warnings: []
|
|
147
|
+
};
|
|
148
|
+
} catch (error) {
|
|
149
|
+
return {
|
|
150
|
+
errors: [parserDiagnostic({
|
|
151
|
+
dialect: "Less",
|
|
152
|
+
error,
|
|
153
|
+
filePath,
|
|
154
|
+
source: input
|
|
155
|
+
})],
|
|
156
|
+
warnings: []
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
export { safeParseWith as n, parseWith as t };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/trivia-labels.ts
|
|
2
|
+
/**
|
|
3
|
+
* Root-trivia label selection for the Less 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 = ["lineComment", "blockComment"];
|
|
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 Less 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 = ["lineComment", "blockComment"];
|
|
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 Less 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 Less to a CST whose spans carry line and column facts. */
|
|
14
|
+
function parseLessCst(input, startRule = "Stylesheet", options) {
|
|
15
|
+
return (0, _jesscss_css_parser_cst_host.parseCst)(src_grammar_cst_positions_js.lessCstPositionsGrammar, input, startRule, options, require_trivia_labels.commentTriviaLabels);
|
|
16
|
+
}
|
|
17
|
+
/** Incremental (`.edit()`-able) Less document with line and column facts. */
|
|
18
|
+
function parseLessDoc(input, startRule = "Stylesheet") {
|
|
19
|
+
return (0, _jesscss_css_parser_cst_host.parseDocCst)(src_grammar_cst_positions_js.lessCstPositionsGrammar, input, startRule);
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
exports.commentTriviaLabels = require_trivia_labels.commentTriviaLabels;
|
|
23
|
+
exports.parseLessCst = parseLessCst;
|
|
24
|
+
exports.parseLessDoc = parseLessDoc;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type CssCstNode, type CssCstParseOptions, type CssCstParseResult, type ParseDoc } from '@jesscss/css-parser/cst-host';
|
|
2
|
+
export { commentTriviaLabels } from '../trivia-labels.js';
|
|
3
|
+
/** Parse Less to a CST whose spans carry line and column facts. */
|
|
4
|
+
export declare function parseLessCst(input: string, startRule?: string, options?: CssCstParseOptions): CssCstParseResult;
|
|
5
|
+
/** Incremental (`.edit()`-able) Less document with line and column facts. */
|
|
6
|
+
export declare function parseLessDoc(input: string, startRule?: string): ParseDoc<CssCstNode>;
|
|
7
|
+
export type { CssCstChild as LessCstChild, CssCstError as LessCstError, CssCstLeaf as LessCstLeaf, CssCstNode as LessCstNode, CssCstParseOptions as LessCstParseOptions, CssCstParseResult as LessCstParseResult, CssCstType as LessCstType } from '@jesscss/css-parser/cst-host';
|
|
8
|
+
//# sourceMappingURL=positions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"positions.d.ts","sourceRoot":"","sources":["../../src/cst/positions.ts"],"names":[],"mappings":"AAQA,OAAO,EAAyB,KAAK,UAAU,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAGtJ,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D,mEAAmE;AACnE,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,SAAS,SAAe,EACxB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,iBAAiB,CAQnB;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,SAAS,SAAe,GACvB,QAAQ,CAAC,UAAU,CAAC,CAMtB;AAED,YAAY,EACV,WAAW,IAAI,YAAY,EAC3B,WAAW,IAAI,YAAY,EAC3B,UAAU,IAAI,WAAW,EACzB,UAAU,IAAI,WAAW,EACzB,kBAAkB,IAAI,mBAAmB,EACzC,iBAAiB,IAAI,kBAAkB,EACvC,UAAU,IAAI,WAAW,EAC1B,MAAM,8BAA8B,CAAC"}
|
|
@@ -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 { lessCstPositionsGrammar } from "../grammar/cst/positions.js";
|
|
4
|
+
//#region src/cst/positions.ts
|
|
5
|
+
/**
|
|
6
|
+
* The line-aware Less 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 Less to a CST whose spans carry line and column facts. */
|
|
13
|
+
function parseLessCst(input, startRule = "Stylesheet", options) {
|
|
14
|
+
return parseCst(lessCstPositionsGrammar, input, startRule, options, commentTriviaLabels);
|
|
15
|
+
}
|
|
16
|
+
/** Incremental (`.edit()`-able) Less document with line and column facts. */
|
|
17
|
+
function parseLessDoc(input, startRule = "Stylesheet") {
|
|
18
|
+
return parseDocCst(lessCstPositionsGrammar, input, startRule);
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { commentTriviaLabels, parseLessCst, parseLessDoc };
|
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 Less to a CST. Spans carry offsets only; for line/column facts import
|
|
8
|
+
* the same functions from `@jesscss/less-parser/cst/positions`, which binds the
|
|
9
|
+
* line-aware compiled table. This entry never loads that table.
|
|
10
|
+
*/
|
|
5
11
|
function parseLessCst(input, startRule = "Stylesheet", options) {
|
|
6
|
-
return (0,
|
|
12
|
+
return (0, _jesscss_css_parser_cst_host.parseCst)(src_grammar_cst_js.lessCstGrammar, input, startRule, options, require_trivia_labels.commentTriviaLabels);
|
|
7
13
|
}
|
|
8
14
|
/** Incremental (`.edit()`-able) Less document — see `parseDocCst`. */
|
|
9
15
|
function parseLessDoc(input, startRule = "Stylesheet") {
|
|
10
|
-
return (0,
|
|
16
|
+
return (0, _jesscss_css_parser_cst_host.parseDocCst)(src_grammar_cst_js.lessCstGrammar, input, startRule);
|
|
11
17
|
}
|
|
12
18
|
//#endregion
|
|
19
|
+
exports.commentTriviaLabels = require_trivia_labels.commentTriviaLabels;
|
|
13
20
|
exports.parseLessCst = parseLessCst;
|
|
14
21
|
exports.parseLessDoc = parseLessDoc;
|
package/lib/cst.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
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 Less to a CST. Spans carry offsets only; for line/column facts import
|
|
5
|
+
* the same functions from `@jesscss/less-parser/cst/positions`, which binds the
|
|
6
|
+
* line-aware compiled table. This entry never loads that table.
|
|
7
|
+
*/
|
|
2
8
|
export declare function parseLessCst(input: string, startRule?: string, options?: CssCstParseOptions): CssCstParseResult;
|
|
3
9
|
/** Incremental (`.edit()`-able) Less document — see `parseDocCst`. */
|
|
4
10
|
export declare function parseLessDoc(input: string, startRule?: string): ParseDoc<CssCstNode>;
|
|
5
|
-
export type { CssCstChild as LessCstChild, CssCstError as LessCstError, CssCstLeaf as LessCstLeaf, CssCstNode as LessCstNode, CssCstParseOptions as LessCstParseOptions, CssCstParseResult as LessCstParseResult, CssCstType as LessCstType } from '@jesscss/css-parser/cst';
|
|
11
|
+
export type { CssCstChild as LessCstChild, CssCstError as LessCstError, CssCstLeaf as LessCstLeaf, CssCstNode as LessCstNode, CssCstParseOptions as LessCstParseOptions, CssCstParseResult as LessCstParseResult, CssCstType as LessCstType } from '@jesscss/css-parser/cst-host';
|
|
6
12
|
//# sourceMappingURL=cst.d.ts.map
|
package/lib/cst.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cst.d.ts","sourceRoot":"","sources":["../src/cst.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cst.d.ts","sourceRoot":"","sources":["../src/cst.ts"],"names":[],"mappings":"AACA,OAAO,EAAyB,KAAK,UAAU,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAGtJ,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,SAAS,SAAe,EACxB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,iBAAiB,CAQnB;AAED,sEAAsE;AACtE,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,SAAS,SAAe,GACvB,QAAQ,CAAC,UAAU,CAAC,CAMtB;AAED,YAAY,EACV,WAAW,IAAI,YAAY,EAC3B,WAAW,IAAI,YAAY,EAC3B,UAAU,IAAI,WAAW,EACzB,UAAU,IAAI,WAAW,EACzB,kBAAkB,IAAI,mBAAmB,EACzC,iBAAiB,IAAI,kBAAkB,EACvC,UAAU,IAAI,WAAW,EAC1B,MAAM,8BAA8B,CAAC"}
|
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 { lessCstGrammar } from "./grammar/cst.js";
|
|
3
4
|
//#region src/cst.ts
|
|
5
|
+
/**
|
|
6
|
+
* Parse Less to a CST. Spans carry offsets only; for line/column facts import
|
|
7
|
+
* the same functions from `@jesscss/less-parser/cst/positions`, which binds the
|
|
8
|
+
* line-aware compiled table. This entry never loads that table.
|
|
9
|
+
*/
|
|
4
10
|
function parseLessCst(input, startRule = "Stylesheet", options) {
|
|
5
|
-
return parseCst(lessCstGrammar, input, startRule, options);
|
|
11
|
+
return parseCst(lessCstGrammar, input, startRule, options, commentTriviaLabels);
|
|
6
12
|
}
|
|
7
13
|
/** Incremental (`.edit()`-able) Less document — see `parseDocCst`. */
|
|
8
14
|
function parseLessDoc(input, startRule = "Stylesheet") {
|
|
9
15
|
return parseDocCst(lessCstGrammar, input, startRule);
|
|
10
16
|
}
|
|
11
17
|
//#endregion
|
|
12
|
-
export { parseLessCst, parseLessDoc };
|
|
18
|
+
export { commentTriviaLabels, parseLessCst, parseLessDoc };
|