@jesscss/less-parser 2.0.0-alpha.1 → 2.0.0-alpha.11

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 CHANGED
@@ -1,36 +1,166 @@
1
- # Jess - Less parser
2
-
3
- Translates a Less string to a Jess AST.
4
-
5
- ## Ideas
6
-
7
- ### Converting Less 1.x-5.x to Less 6
8
-
9
- 1. Auto-wrap parens around division to dis-ambiguate.
10
- 2. Convert `@import` to `@use` and `@include` syntax.
11
- 3. Throw errors on `@plugin` and ask to refactor with `@from`
12
- 4. Convert function references to `@from '#less' ([func])`
13
- 5. Add parentheses after mixin calls e.g. `.ns > .mixin;` to `.ns.mixin();`
14
- 6. Convert local imports from `@import 'local'` to `@include './local.less'`
15
- 7. Convert `@import (less) './file.css';` to `@include './file.css' as less;`
16
- 8. Convert `@import (inline) './file.css';` to `@include './file.css' as text;`
17
- 9. Convert `@import (reference) './file.less';` to `@use './file.less';`
18
- 10. Files that consume variables, mixins, or rules (like with extend) should have a `@use` added.
19
- 11. Don't allow `.class` as a value in a declaration. Convert to `\.class` e.g. `@foo: .class` should be converted to `@foo: \.class` (or `selector(.class)`?).
20
- 12. In a custom property value, convert `@variable` to `@{variable}`.
21
-
22
-
23
- ### Converting Less 1.x-4.x to Jess
24
-
25
- 1. Auto-wrap expressions (like math) with `$()`
26
- 2. Convert mixin definitions `.my-mixin()` to `@mixin my-mixin()`
27
- 3. Convert mixin calls to function calls: `#ns > .mixin()` to `$ns.mixin()`
28
- 4. Throw errors on mixed case mixins: `.my-mixin()` and `.myMixin()`
29
- 5. Throw errors on mixed hash and class mixins: `#my-mixin()` and `.my-mixin()`
30
- 5. Convert variable declarations `@my-var` with `$my-var`
31
- 6. Convert interpolated vars `@{my-var}` to `$(my-var)`
32
- 7. Convert property references `$prop` to `$[prop]`
33
- 8. Convert color names in expressions to hex values (or wrapped in `color()`?) (because Jess doesn't support color keywords in expressions). Alternatively, should Jess allow `keyword` to denote keywords?
34
- 9. Convert `@rest...` to `...rest`
35
- 10. Convert `.rules()` to `@include .rules()` if `.rules` is a selector. What if it's a selector and mixin? Maybe something like `@include .rules, $rules();`? This might change the execution order from Less though.
36
- 11. Convert `@foo: extract(@bar, 1)` to `@let foo: $bar[0];`?
1
+ # @jesscss/less-parser
2
+
3
+ The Less grammar, layered on the CSS base parser, with core-free CST entry points.
4
+
5
+ > **Status: alpha.** Part of [Jess](https://github.com/jesscss/jess). The broader
6
+ > language/tooling picture is still early. Expect gaps and
7
+ > [report bugs](https://github.com/jesscss/jess/issues). Docs live at
8
+ > [jesscss.github.io](https://jesscss.github.io/).
9
+
10
+ ## What it is
11
+
12
+ The Less grammar is the shared CSS grammar plus a Less delta:
13
+ `lessGrammar = compose([cssGrammar, <Less delta>])`. It adds `@variable` /
14
+ `@{interpolation}`, mixins, and the rest of Less on top of the spec-aligned CSS
15
+ base in [`@jesscss/css-parser`](https://www.npmjs.com/package/@jesscss/css-parser),
16
+ built on [parseman](https://www.npmjs.com/package/parseman) **the fastest
17
+ general-purpose JavaScript parser** in its
18
+ [published benchmarks](https://matthew-dean.github.io/parseman/guide/benchmarks)
19
+ (see `@jesscss/css-parser` for figures and engineering details). It is the parser
20
+ Jess uses when it compiles `.less` the "Now" tier of the language roadmap, and
21
+ the one dialect shipping in the alpha.
22
+
23
+ The default `parse()` operation constructs canonical AST v2 `Stylesheet` directly
24
+ through parser-local Parseman reductions. Use the explicit `./cst` entry when a
25
+ language-service or document consumer needs a CST. The package has no
26
+ core-owned parser driver or AST construction host.
27
+
28
+ ## Install
29
+
30
+ ```sh
31
+ npm install @jesscss/less-parser
32
+ ```
33
+
34
+ `@jesscss/core` is an optional peer for consumers using the default AST v2
35
+ `parse()` result. The explicit CST and grammar subpaths remain core-free.
36
+ Those explicit entries expose Parseman types and grammar values, so consumers
37
+ of them must also provide the package's `parseman` peer.
38
+
39
+ ## Canonical AST parsing
40
+
41
+ ```js
42
+ import { parse } from '@jesscss/less-parser'
43
+
44
+ const stylesheet = parse('@c: red;\n.foo { color: @c; }')
45
+
46
+ stylesheet.type // 'Stylesheet'
47
+ ```
48
+
49
+ ## Standalone usage (core-free)
50
+
51
+ ```js
52
+ import { parseLessCst } from '@jesscss/less-parser/cst'
53
+
54
+ const result = parseLessCst('@c: red;\n.foo { color: @c; }')
55
+
56
+ result.ok // true
57
+ result.errors // ParseError[] (empty when ok)
58
+ result.unconsumedFrom // index of first unparsed char, or null
59
+ result.tree // the CST root (a StyleSheet node)
60
+ ```
61
+
62
+ Signature:
63
+
64
+ ```ts
65
+ parseLessCst(input: string, startRule = 'Stylesheet', options?: { collapse?: boolean }): LessCstParseResult
66
+ ```
67
+
68
+ Pass a different `startRule` (any capitalized grammar rule, e.g. `'SelectorList'`, `'Declaration'`) to parse a fragment.
69
+
70
+ ## Public API
71
+
72
+ | Entry | Export | Purpose |
73
+ | --- | --- | --- |
74
+ | `@jesscss/less-parser/cst` | `parseLessCst` | Core-free parse of a Less string to a CST. |
75
+ | `@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`. |
77
+ | `@jesscss/less-parser` (`.`) | `parse` | Parse Less directly to canonical AST v2 `Stylesheet`. It does not load the CST grammar. |
78
+
79
+ ## Default CST shape
80
+
81
+ The CST is parseman's, produced by the shared `cssCstBuildHost`. Three kinds of node:
82
+
83
+ - **node** — `{ _tag: 'node', type, grammarType, span: { start, end }, state, children }` (`grammarType` = raw rule name; `type` = friendly public name).
84
+ - **leaf** — `{ _tag: 'leaf', value, span }` for terminals.
85
+ - **error** — `{ _tag: 'error', type, span, expected, children, state }` where recovery happened.
86
+
87
+ Spans are `[start, end)` offsets; whitespace, block comments, **and Less line comments (`//`)** are trivia and do not appear as children.
88
+
89
+ Parsing `@c: red;\n.foo { color: @c; }` yields (abridged):
90
+
91
+ ```jsonc
92
+ {
93
+ "_tag": "node", "type": "StyleSheet", "grammarType": "Stylesheet",
94
+ "children": [
95
+ { "_tag": "node", "type": "VarDeclaration", "grammarType": "VarDeclaration", "span": { "start": 0, "end": 8 },
96
+ "children": [
97
+ { "_tag": "leaf", "value": "@c" }, { "_tag": "leaf", "value": ":" },
98
+ { "_tag": "node", "type": "NamedColor", "grammarType": "NamedColor",
99
+ "children": [ { "_tag": "leaf", "value": "red" } ] },
100
+ { "_tag": "leaf", "value": ";" }
101
+ ] },
102
+ { "_tag": "node", "type": "QualifiedRule", "grammarType": "Ruleset", "span": { "start": 9, "end": 28 },
103
+ "children": [
104
+ { "_tag": "leaf", "value": ".foo" }, { "_tag": "leaf", "value": "{" },
105
+ { "_tag": "node", "type": "Declaration", "grammarType": "Declaration",
106
+ "children": [
107
+ { "_tag": "leaf", "value": "color" }, { "_tag": "leaf", "value": ":" },
108
+ { "_tag": "node", "type": "Reference", "grammarType": "Reference",
109
+ "children": [ { "_tag": "leaf", "value": "@c" } ] },
110
+ { "_tag": "leaf", "value": ";" }
111
+ ] },
112
+ { "_tag": "leaf", "value": "}" }
113
+ ] }
114
+ ]
115
+ }
116
+ ```
117
+
118
+ Note the Less-specific nodes: a top-level `@c: …` becomes a `VarDeclaration`, a `@c` value becomes a `Reference`, and the color keyword `red` parses as `NamedColor` (the CSS-only grammar has no such rule — see `@jesscss/css-parser`).
119
+
120
+ Pass `{ collapse: true }` to unwrap single-child wrapper types (`Reference`, `NamedColor`, `InterpolatedSelector`) into their child.
121
+
122
+ ### Name-independent condition arguments
123
+
124
+ A top-level condition operator (`> < >= <= = and or not`) inside **any** call's argument parses as a `Condition` node — there is **no** parse-time name-dispatch on `if`/`boolean`. `if(@a > 5, 1, 2)`, `boolean(not(2 < 1))`, `#ns.if(@a > 5)`, and `foo(@a > 5 and @b < 2)` all route through the ordinary function/mixin `Call` production; the shared call-arg rule (`ArgCondition` → `CondArgOr`/`CondArgAnd`/`CondArgTerm`) layers the condition-operator precedence chain on top of the normal value production. The layer is structurally gated: it only matches when a real operator is present, so a plain value / space-list argument (and mixin-definition params) fall through to the unchanged `valueSequence` byte-identically. Eval treats `if`/`boolean` as ordinary registered functions that consume the parsed `Condition`, so this is a parse-only unification (a deliberate v5 loosening vs Less 4.x, which name-dispatched and errored on the namespaced/generic forms).
125
+
126
+ One known gap: a namespace/accessor call in **value** position (`b: #ns.if(@a > 5)`, `b: .if(@a > 5)`) is reassembled from a raw permissive-paren capture (`_buildRefCallArgs`), a separate shallow path that does not run the condition layer — its args stay a value list. Statement-position (`#ns.if(@a > 5) { }` / bare `#ns.if(@a > 5)`) and all function-call forms are covered.
127
+
128
+ ## Extending with your own builders
129
+
130
+ The grammar is decoupled from the tree it builds. Every capitalized rule is a parseman `node()`; when you drive a grammar with a `build` host, each `node()` calls your host instead of constructing the default CST. Use parseman's `run` with your own host and the grammar's trivia rule:
131
+
132
+ ```js
133
+ import { run } from 'parseman'
134
+ import { lessGrammar } from '@jesscss/less-parser/grammar'
135
+
136
+ const myHost = (type, children, fields, span) => ({ type, span, children: children.filter(Boolean) })
137
+
138
+ const result = run(lessGrammar.Stylesheet, '@c: red; .foo { color: @c; }', {
139
+ build: myHost,
140
+ trivia: lessGrammar.rw // Less trivia = whitespace + block + line comments
141
+ })
142
+
143
+ result.value // the root node your host returned
144
+ ```
145
+
146
+ The `BuildHost` signature (from parseman):
147
+
148
+ ```ts
149
+ type BuildHost = (
150
+ type: string,
151
+ children: readonly unknown[],
152
+ fields: FieldMap | undefined,
153
+ span: { start: number; end: number },
154
+ rawChildren: readonly unknown[],
155
+ triviaLog: readonly number[],
156
+ state: unknown
157
+ ) => unknown
158
+ ```
159
+
160
+ `parseLessCst(...)` is this pattern with the shared `cssCstBuildHost` (see `@jesscss/css-parser`, `src/cst.ts`) as a reference host.
161
+
162
+ ## Part of Jess
163
+
164
+ This package is developed as part of [Jess](https://github.com/jesscss/jess).
165
+ Jess translates a Less string into the core Jess AST, which the compiler then
166
+ evaluates and renders to CSS. Licensed MIT.
package/lib/cst.cjs ADDED
@@ -0,0 +1,14 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_grammar = require("./grammar2.cjs");
3
+ let _jesscss_css_parser_cst = require("@jesscss/css-parser/cst");
4
+ //#region src/cst.ts
5
+ function parseLessCst(input, startRule = "Stylesheet", options) {
6
+ return (0, _jesscss_css_parser_cst.parseCst)(require_grammar.lessCstGrammar, input, startRule, options);
7
+ }
8
+ /** Incremental (`.edit()`-able) Less document — see `parseDocCst`. */
9
+ function parseLessDoc(input, startRule = "Stylesheet") {
10
+ return (0, _jesscss_css_parser_cst.parseDocCst)(require_grammar.lessCstGrammar, input, startRule);
11
+ }
12
+ //#endregion
13
+ exports.parseLessCst = parseLessCst;
14
+ exports.parseLessDoc = parseLessDoc;
package/lib/cst.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { type CssCstNode, type CssCstParseOptions, type CssCstParseResult, type ParseDoc } from '@jesscss/css-parser/cst';
2
+ export declare function parseLessCst(input: string, startRule?: string, options?: CssCstParseOptions): CssCstParseResult;
3
+ /** Incremental (`.edit()`-able) Less document — see `parseDocCst`. */
4
+ 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';
6
+ //# sourceMappingURL=cst.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cst.d.ts","sourceRoot":"","sources":["../src/cst.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,UAAU,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAGjJ,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,SAAS,SAAe,EACxB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,iBAAiB,CAEnB;AAED,sEAAsE;AACtE,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,SAAe,GAAG,QAAQ,CAAC,UAAU,CAAC,CAE1F;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,yBAAyB,CAAC"}
package/lib/cst.js ADDED
@@ -0,0 +1,12 @@
1
+ import { n as lessCstGrammar } from "./grammar2.js";
2
+ import { parseCst, parseDocCst } from "@jesscss/css-parser/cst";
3
+ //#region src/cst.ts
4
+ function parseLessCst(input, startRule = "Stylesheet", options) {
5
+ return parseCst(lessCstGrammar, input, startRule, options);
6
+ }
7
+ /** Incremental (`.edit()`-able) Less document — see `parseDocCst`. */
8
+ function parseLessDoc(input, startRule = "Stylesheet") {
9
+ return parseDocCst(lessCstGrammar, input, startRule);
10
+ }
11
+ //#endregion
12
+ export { parseLessCst, parseLessDoc };
@@ -0,0 +1,5 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_grammar = require("./grammar2.cjs");
3
+ exports.lessAstGrammar = require_grammar.lessAstGrammar;
4
+ exports.lessCstGrammar = require_grammar.lessCstGrammar;
5
+ exports.lessGrammar = require_grammar.lessGrammar;
@@ -0,0 +1,5 @@
1
+ export declare const lessGrammar: Record<string, import("parseman").FusedRule>;
2
+ export declare const lessAstGrammar: Record<string, import("parseman").FusedRule>;
3
+ /** Public Less CST artifact: the same grammar factory compiled in CST mode. */
4
+ export declare const lessCstGrammar: Record<string, import("parseman").FusedRule>;
5
+ //# sourceMappingURL=grammar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grammar.d.ts","sourceRoot":"","sources":["../src/grammar.ts"],"names":[],"mappings":"AAuvLA,eAAO,MAAM,WAAW,8CAAwL,CAAC;AACjN,eAAO,MAAM,cAAc,8CAAc,CAAC;AAE1C,+EAA+E;AAC/E,eAAO,MAAM,cAAc,8CAAyM,CAAC"}
package/lib/grammar.js ADDED
@@ -0,0 +1,2 @@
1
+ import { n as lessCstGrammar, r as lessGrammar, t as lessAstGrammar } from "./grammar2.js";
2
+ export { lessAstGrammar, lessCstGrammar, lessGrammar };