New:Microsoft Teams Notifications Are Now Available in Socket.Learn more →
Get Started

@jesscss/less-parser

Package Overview
Dependencies
Maintainers
1
Versions
28
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@jesscss/less-parser

Jess LESS parser

latest
npmnpm
Version
2.0.0-alpha.7
Version published
Weekly downloads
628
-35.79%
Maintainers
1
Weekly downloads
 
Created
Source

@jesscss/less-parser

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:

  • As part of Jess — the default . 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.
  • As a standalone CST parser — the ./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.

Install

npm install @jesscss/less-parser

@jesscss/core is an optional peer dependency — needed only for the core-coupled . entry, not for ./cst or ./grammar.

Standalone usage (core-free)

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.

Public API

EntryExportPurpose
@jesscss/less-parser/cstparseLessCstCore-free parse of a Less string to a CST.
@jesscss/less-parser/cstLessCstNode, LessCstLeaf, LessCstError, LessCstChild, LessCstParseResult, LessCstType (types)CST type definitions (aliases of the shared @jesscss/css-parser/cst types).
@jesscss/less-parser/grammarlessGrammarThe 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/jessLessParser, LessGrammar, parseLessFn, …Internal Jess-facing surface.

Default CST shape

The CST is parseman's, produced by the shared cssCstBuildHost. Three kinds of node:

  • node — { _tag: 'node', type, grammarType, span: { start, end }, state, children } (grammarType = raw rule name; type = friendly public name).
  • leaf — { _tag: 'leaf', value, span } for terminals.
  • error — { _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.

Name-independent condition arguments

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.

Extending with your own builders

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.

Part of Jess

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.

Converting Less 1.x-5.x to Less 6

  • Auto-wrap parens around division to dis-ambiguate.
  • Convert @import to @use and @include syntax.
  • Throw errors on @plugin and ask to refactor with @from
  • Convert function references to @from '#less' ([func])
  • Add parentheses after mixin calls e.g. .ns > .mixin; to .ns.mixin();
  • Convert local imports from @import 'local' to @include './local.less'
  • Convert @import (less) './file.css'; to @include './file.css' as less;
  • Convert @import (inline) './file.css'; to @include './file.css' as text;
  • Convert @import (reference) './file.less'; to @use './file.less';
  • Files that consume variables, mixins, or rules (like with extend) should have a @use added.
  • 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)?).
  • In a custom property value, convert @variable to @{variable}.

Converting Less 1.x-4.x to Jess

  • Auto-wrap expressions (like math) with $()
  • Convert mixin definitions .my-mixin() to @mixin my-mixin()
  • Convert mixin calls to function calls: #ns > .mixin() to $ns.mixin()
  • Throw errors on mixed case mixins: .my-mixin() and .myMixin()
  • Throw errors on mixed hash and class mixins: #my-mixin() and .my-mixin()
  • Convert variable declarations @my-var with $my-var
  • Convert interpolated vars @{my-var} to $(my-var)
  • Convert property references $prop to $[prop]
  • 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?
  • Convert @rest... to ...rest
  • 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.
  • Convert @foo: extract(@bar, 1) to @let foo: $bar[0];?

FAQs

Package last updated on 12 Jul 2026

Related posts