
Company News
Socket Joins New OpenJS Program to Fund Node.js Security Work
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.
@jesscss/less-parser
Advanced tools
A Less parser built on parseman. The grammar is the CSS grammar plus a Less delta: lessGrammar = compose([cssGrammar, <Less delta>]). It adds @variable / @{interpolation}, mixins, and the rest of Less on top of the shared CSS base in @jesscss/css-parser.
Two ways to use it:
. entry is wired into @jesscss/core and produces the core AST the Jess compiler evaluates (this is what runs when Jess compiles Less). This is the internal, core-coupled path../cst entry has no dependency on @jesscss/core. Install just this package and parse Less source text into a concrete syntax tree (CST). You can also plug your own builders onto the grammar to produce your own AST instead of the default CST.npm install @jesscss/less-parser
@jesscss/core is an optional peer dependency — needed only for the core-coupled . entry, not for ./cst or ./grammar.
import { parseLessCst } from '@jesscss/less-parser/cst'
const result = parseLessCst('@c: red;\n.foo { color: @c; }')
result.ok // true
result.errors // ParseError[] (empty when ok)
result.unconsumedFrom // index of first unparsed char, or null
result.tree // the CST root (a StyleSheet node)
Signature:
parseLessCst(input: string, startRule = 'Stylesheet', options?: { collapse?: boolean }): LessCstParseResult
Pass a different startRule (any capitalized grammar rule, e.g. 'SelectorList', 'Declaration') to parse a fragment.
| Entry | Export | Purpose |
|---|---|---|
@jesscss/less-parser/cst | parseLessCst | Core-free parse of a Less string to a CST. |
@jesscss/less-parser/cst | LessCstNode, LessCstLeaf, LessCstError, LessCstChild, LessCstParseResult, LessCstType (types) | CST type definitions (aliases of the shared @jesscss/css-parser/cst types). |
@jesscss/less-parser/grammar | lessGrammar | The compiled Less grammar (a rule map). Extend it with compose() or drive it directly with parseman's run. |
@jesscss/less-parser (.) | LessParser (also Parser), parseLessFn, lessGrammar, tokens, … | The Jess-internal barrel. Core-coupled (the functional parser builds the core AST). Prefer ./cst if you don't need @jesscss/core. |
@jesscss/less-parser/jess | LessParser, LessGrammar, parseLessFn, … | Internal Jess-facing surface. |
The CST is parseman's, produced by the shared cssCstBuildHost. Three kinds of node:
{ _tag: 'node', type, grammarType, span: { start, end }, state, children } (grammarType = raw rule name; type = friendly public name).{ _tag: 'leaf', value, span } for terminals.{ _tag: 'error', type, span, expected, children, state } where recovery happened.Spans are [start, end) offsets; whitespace, block comments, and Less line comments (//) are trivia and do not appear as children.
Parsing @c: red;\n.foo { color: @c; } yields (abridged):
{
"_tag": "node", "type": "StyleSheet", "grammarType": "Stylesheet",
"children": [
{ "_tag": "node", "type": "VarDeclaration", "grammarType": "VarDeclaration", "span": { "start": 0, "end": 8 },
"children": [
{ "_tag": "leaf", "value": "@c" }, { "_tag": "leaf", "value": ":" },
{ "_tag": "node", "type": "NamedColor", "grammarType": "NamedColor",
"children": [ { "_tag": "leaf", "value": "red" } ] },
{ "_tag": "leaf", "value": ";" }
] },
{ "_tag": "node", "type": "QualifiedRule", "grammarType": "Ruleset", "span": { "start": 9, "end": 28 },
"children": [
{ "_tag": "leaf", "value": ".foo" }, { "_tag": "leaf", "value": "{" },
{ "_tag": "node", "type": "Declaration", "grammarType": "Declaration",
"children": [
{ "_tag": "leaf", "value": "color" }, { "_tag": "leaf", "value": ":" },
{ "_tag": "node", "type": "Reference", "grammarType": "Reference",
"children": [ { "_tag": "leaf", "value": "@c" } ] },
{ "_tag": "leaf", "value": ";" }
] },
{ "_tag": "leaf", "value": "}" }
] }
]
}
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).
Pass { collapse: true } to unwrap single-child wrapper types (Reference, NamedColor, InterpolatedSelector) into their child.
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).
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.
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:
import { run } from 'parseman'
import { lessGrammar } from '@jesscss/less-parser/grammar'
const myHost = (type, children, fields, span) => ({ type, span, children: children.filter(Boolean) })
const result = run(lessGrammar.Stylesheet, '@c: red; .foo { color: @c; }', {
build: myHost,
trivia: lessGrammar.rw // Less trivia = whitespace + block + line comments
})
result.value // the root node your host returned
The BuildHost signature (from parseman):
type BuildHost = (
type: string,
children: readonly unknown[],
fields: FieldMap | undefined,
span: { start: number; end: number },
rawChildren: readonly unknown[],
triviaLog: readonly number[],
state: unknown
) => unknown
parseLessCst(...) is this pattern with the shared cssCstBuildHost (see @jesscss/css-parser, src/cst.ts) as a reference host.
This package is developed as part of Jess. Jess translates a Less string into a Jess AST; the sections below track the migration rules that entails.
@import to @use and @include syntax.@plugin and ask to refactor with @from@from '#less' ([func]).ns > .mixin; to .ns.mixin();@import 'local' to @include './local.less'@import (less) './file.css'; to @include './file.css' as less;@import (inline) './file.css'; to @include './file.css' as text;@import (reference) './file.less'; to @use './file.less';@use added..class as a value in a declaration. Convert to \.class e.g. @foo: .class should be converted to @foo: \.class (or selector(.class)?).@variable to @{variable}.$().my-mixin() to @mixin my-mixin()#ns > .mixin() to $ns.mixin().my-mixin() and .myMixin()#my-mixin() and .my-mixin()@my-var with $my-var@{my-var} to $(my-var)$prop to $[prop]color()?) (because Jess doesn't support color keywords in expressions). Alternatively, should Jess allow keyword to denote keywords?@rest... to ...rest.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.@foo: extract(@bar, 1) to @let foo: $bar[0];?FAQs
Jess LESS parser
The npm package @jesscss/less-parser receives a total of 601 weekly downloads. As such, @jesscss/less-parser popularity was classified as not popular.
We found that @jesscss/less-parser demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Company News
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.

Research
/Security News
A malicious Firefox extension fetches its payload after installation to evade detection, steal Google session cookies, and automate account takeover.